def _init_sample_buttons(self): lg.info('-- SET SAMPLE BUTTONS') def next_sample(): lg.info('>> NEXT SAMPLE') if self.s < self.ns: self.env.doc.hold('collect') self.s += 1 self.sample_div.text = ' {} / {}'.format(self.s, self.ns) self.env.cur_nearby_prof = None # to reset the extra stt profile to plot self.env.dt_next_sample = True self._update_dt_sample() self.env.doc.unhold() def previous_sample(): lg.info('>> PREVIOUS SAMPLE') if self.s > 1: self.env.doc.hold('collect') self.s -= 1 self.sample_div.text = ' {} / {}'.format(self.s, self.ns) self.env.cur_nearby_prof = None self.env.dt_previous_sample = True self._update_dt_sample() self.env.doc.unhold() self.next_bt = Button(label=">", button_type="success", width=30) self.sample_div = Div( text='0 / 0', width=100, height=30, css_classes=['sample_div'], ) self.previous_bt = Button(label="<", button_type="success", width=30) self.next_bt.on_click(next_sample) self.previous_bt.on_click(previous_sample)
def _init_profile_nav(self): def next_profile(): if self.nearby_prof_cb: lg.info('-- NEXT PROFILE') s = self.env.stations next_pos = s.index(self.env.cur_nearby_prof) + 1 if next_pos < len(self.env.stations): if s[next_pos] == self.env.stt_to_select: next_pos = next_pos + 1 if next_pos < len(self.env.stations): self.env.cur_nearby_prof = s[next_pos] self.env.bk_sources._upd_prof_srcs(force_selection=True) self.nearby_prof_div.text = str( int(self.env.cur_nearby_prof)) # adjust disabled buttons if next_pos + 1 == len(self.env.stations): self.next_prof_bt.disabled = True self.previous_prof_bt.disabled = False def previous_profile(): lg.info('-- PREVIOUS PROFILE') if self.nearby_prof_cb: s = self.env.stations previous_pos = s.index(self.env.cur_nearby_prof) - 1 if previous_pos >= 0: if s[previous_pos] == self.env.stt_to_select: previous_pos = previous_pos - 1 if previous_pos >= 0: self.env.cur_nearby_prof = s[previous_pos] self.env.bk_sources._upd_prof_srcs(force_selection=True) self.nearby_prof_div.text = str( int(self.env.cur_nearby_prof)) # adjust disabled buttons if previous_pos == 0: self.previous_prof_bt.disabled = True self.next_prof_bt.disabled = False self.next_prof_bt = Button( width=30, disabled=True, label=">", button_type="success", ) self.nearby_prof_div = Div( width=100, height=30, text='None', css_classes=['cur_stt'], ) self.previous_prof_bt = Button( width=30, disabled=True, label="<", button_type="success", ) self.next_prof_bt.on_click(next_profile) self.previous_prof_bt.on_click(previous_profile)
def _init_flags_control_header(self): lg.info('-- FLAGS CONTROL HEADER') self.all_flags_vb_bt = Button( name='all_flags_bt', label='', width=30, css_classes=['eye_bt'], ) def all_flags_vb_bt_callback(): lg.info('-- ALL FLAGS VISIBLE CALLBACK') self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) all_flags_bt = self.env.doc.select_one(dict(name='all_flags_bt')) eye_slash_bt = True if 'eye_slash_bt' in all_flags_bt.css_classes else False if eye_slash_bt: self.env.doc.set_select(selector=dict(tags=['vb_bt']), updates=dict(css_classes=['eye_bt'])) else: self.env.doc.set_select( selector=dict(tags=['vb_bt']), updates=dict(css_classes=['eye_slash_bt'])) new_visible_flags = [] if 'eye_bt' in all_flags_bt.css_classes: all_flags_bt.css_classes = ['eye_slash_bt'] else: new_visible_flags = self.all_flags_list all_flags_bt.css_classes = ['eye_bt'] lg.info('>> NEW VISIBLE FLAGS: {}'.format(new_visible_flags)) self._update_visible_flags(new_visible_flags) self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) self.all_flags_vb_bt.on_click(all_flags_vb_bt_callback) # TODO: replace this div with the flag selection dropdown # or maybe there would be too many control on one place flag_controls_title_div = Div( name='flag_controls_title', text='All the flags', width=100, height=25, css_classes=['flag_controls_title'], ) self.flags_control_header_row = row( children=[self.all_flags_vb_bt, flag_controls_title_div], width=200, height=25, )
def _init_edit_bt(self, flag_index): edit_flag_bt = Button(name='edit_flag_bt_{}'.format(flag_index), label='', width=30, tags=['edit_flag_bt'], css_classes=['edit_flag_bt']) def update_flag_value_edit_bt(flag_index=flag_index): self.update_flag_value(flag_value=flag_index, flag_to_update=None, row_indexes=self.env.selection) edit_flag_bt.on_click(update_flag_value_edit_bt) return edit_flag_bt
def _init_profile_nav(self): def next_profile(): lg.info('-- NEXT PROFILE') if self.nearby_prof_cb: s = self.nearby_prof_select.options next_pos = s.index(f'{self.env.cur_nearby_prof}') + 1 if next_pos < len(s): self.nearby_prof_select.value = s[next_pos] self.env.cur_nearby_prof = float(s[next_pos]) if (self.env.cur_nearby_prof).is_integer(): self.env.cur_nearby_prof = int( self.env.cur_nearby_prof) def previous_profile(): lg.info('-- PREVIOUS PROFILE') if self.nearby_prof_cb: s = self.nearby_prof_select.options previous_pos = s.index(f'{self.env.cur_nearby_prof}') - 1 if previous_pos >= 0: self.nearby_prof_select.value = s[previous_pos] self.env.cur_nearby_prof = float(s[previous_pos]) if (self.env.cur_nearby_prof).is_integer(): self.env.cur_nearby_prof = int( self.env.cur_nearby_prof) self.next_prof_bt = Button( width=30, disabled=True, label=">", button_type="success", ) self.nearby_prof_select = Select( width=80, value=None, options=['None'], css_classes=['nearby_prof_select'], disabled=True, ) self.previous_prof_bt = Button( width=30, disabled=True, label="<", button_type="success", ) self.next_prof_bt.on_click(next_profile) self.previous_prof_bt.on_click(previous_profile)
def init_widgets(): global widgets widgets = WrapBokeh(PAGE_URL, app.logger) widgets.add( "tin_fname", TextInput(title="First Name:", placeholder="first name", css_classes=['tin_fname'])) widgets.add( "tin_lname", TextInput(title="Last Name:", placeholder="last name", css_classes=['tin_lname'])) widgets.add("b_submit", Button(label="Submit", css_classes=['b_submit'])) countries = [('', 'Select Country')] + [(x, x) for x in geo_info.keys()] states = [('', 'Select State')] + [(x, x) for x in geo_info["United States"]] widgets.add( "sel_country", Select(options=countries, value=None, title="Select Country", css_classes=['sel_country'])) widgets.add( "sel_state", Select(options=states, value=None, title="Select State", css_classes=['sel_state'])) widgets.add( "sel_nexturl", Select(options=[('99', 'Select Next Page'), ('0', 'Home'), ('1', 'Ajax Stream Example'), ('3', 'Page C'), ('4', 'Page D')], value=None, title="Select URL", css_classes=['sel_nexturl'])) widgets.init()
def index_toolbar_menu(w, doc_layout, args): w.add( "b_login", Button(label="LOGIN", width=GUI.TITLEBAR_LOGIN_BTN_WIDTH, css_classes=['b_submit'])) title = app.config["app"]["title"] doc_layout.children.append( row(Div(text="""<h1>{}</h1>""".format(title), width=GUI.TITLEBAR_TITLE_WIDTH), Spacer(width=GUI.TITLEBAR_SPACER), w.get("b_login"), sizing_mode="fixed")) w.add_css("b_login", {'button': { 'background-color': '#98FB98', 'min-width': '50px' }}) w.add_css("toolbarclass", {'div': {'background-color': '#5F9EA0'}})
def modify_doc(doc): """Create and modify the bokeh document. Later run by the tornado server.""" city_1_title = 'Search for the first city' city_2_title = 'Search for the second city' # Adding selectors, essentially the controls, to the app. city_1_select = AutocompleteInput(value=city_1, completions=cities_list, min_characters=3, title=city_1_title) city_2_select = AutocompleteInput(value=city_2, completions=cities_list, min_characters=3, title=city_2_title) variable_select = Select(value=variable, title='Variable', options=list(variable_dict.keys())) method_select = Select(value=method, title='Period', options=methods_list) # And a button to toggle display of trend. display_trend = Button(label='Display trends', button_type='success', width=50) # Initial plot for startup of app. plot = create_plot(source_1, source_2, title, variable, trend) def trend_update(): """Custom callback in the event of button changing state.""" # Make use of global variables. global trend, source_1, source_2, title, variable # Switch state of trend(bool) trend = not trend if trend: display_trend.label = 'Hide trends' else: display_trend.label = 'Show trends' layout.children[1] = create_plot(source_1, source_2, title, variable, trend) def update_plot(attrname, old, new): """This is called when any of the widgets are changed, updates the plot""" # Need to access the value of the global variables. global city_1, city_2, source_1, source_2, title, variable, method # Get the new value of cities and variable. city_1 = city_1_select.value city_2 = city_2_select.value variable = variable_select.value method = method_select.value # Set new title of plot based on new variables. title = (method + ' ' + variable.lower() + ' data from ' + city_1 + ' and ' + city_2 + '.') # Get new source data based on the new parameters. source_1 = get_data(city_1, variable, method) source_2 = get_data(city_2, variable, method) # This is what is actually updating the plot, the plot is the second # child of the layout. layout.children[1] = create_plot(source_1, source_2, title, variable, trend) # Looping through all the selectors to detect changes. control_list = [ city_1_select, city_2_select, variable_select, method_select ] for w in control_list: w.on_change('value', update_plot) # Need to treat button differently. display_trend.on_click(trend_update) # Title displayed on page. text = ('<font size="5">Welcome to the climtrend visualization tool.' ' Compare the climate in two cities by using the controls' ' below.</font>') p = Div(text=text, width=300, height=200) controls = column(p, city_1_select, city_2_select, variable_select, method_select, display_trend) # Add the controls of the app and the plot to the first row of the layout. layout = row(controls, plot) doc.add_root(layout) doc.title = 'ClimTrend'
labeling_input = widgetbox(labeling_x_mu, labeling_y_mu, labeling_x_var, labeling_y_var, labeling_thr) inputs = [hijacking_input, good_user_input, labeling_input] # Plot the ROC curve roc_source = ColumnDataSource(data=dict(x=[], y=[])) def train_and_evaluate(): global hijackers, good_users, labeling, roc_source fpr, tpr = calculate_roc(hijackers, good_users, labeling) roc_source.data = dict(x=fpr, y=tpr) train_and_evaluate() roc = figure(title='RoC', x_axis_label='False positive rate', y_axis_label='True Positive Rate', x_range=(0, 1), y_range=(0, 1), width=1000) roc.line([0, 1], [0, 1], color='navy', line_width=1.0, line_dash='dashed') roc.line(x='x', y='y', source=roc_source, color='darkorange', line_width=1.0, line_dash='dashed') button = Button(label='Calculate ROC') button.on_click(train_and_evaluate) # Add the simulation and ROC curve to DOM. curdoc().add_root(column(row(space_plot, *inputs), row(roc, widgetbox(button)))) curdoc().title = 'Recall vs accuracy trade-off'
gms=MultiSelect(title='Select genus',\ options=[x[0] for x in collections.Counter(ptree.leaf_cds.data['genus']).most_common()],\ width=200,height=70) gms.on_change('value',lambda attr,old,new:gmsfunc()) kms=MultiSelect(title='Kingdom',\ options=[x[0] for x in collections.Counter(ptree.leaf_cds.data['superkingdom']).most_common()],\ width=200,height=70) kms.on_change('value',lambda attr,old,new:kmsfunc()) sfms=MultiSelect(title='Subfams',\ options=[x[0] for x in collections.Counter(ptree.leaf_cds.data['subfamstr']).most_common()],\ width=150,height=70) sfms.on_change('value',lambda attr,old,new:sfmsfunc()) tcb=Button(label='CalcTC',width=80,height=40) tcb.on_click(tcbfunc) #splist=list(set(ptree.leaf_cds.data['species'])) #splist.sorted(key=ptree.leaf_cds.data['species']) #acw=AutocompleteInput(title='Organism name',completions=list(set(ptree.leaf_cds.data['species'])),width=200,height=50) acw=AutocompleteInput(title='Organism name',\ completions=[x[0] for x in collections.Counter(ptree.leaf_cds.data['species']).most_common()],\ width=200,height=50) acw.on_change('value',lambda attr,old,new:acfunc()) accacw=AutocompleteInput(title='Accession',\ completions=ptree.leaf_cds.data['gbacc'][:],width=200,height=50) accacw.on_change('value',lambda attr,old,new:accacfunc()) if len(sys.argv)>3:
## there is a bug in bokeh image_rgba to be used later that requires the following flipping ## https://github.com/bokeh/bokeh/issues/1666 logo = logo[::-1] plogo = figure(x_range=(0, 25), y_range=(0, 15), tools=[], plot_width=333, plot_height=250) plogo.xgrid.grid_line_color = None plogo.ygrid.grid_line_color = None plogo.image_rgba(image=[logo], x=[0], y=[0], dw=[25], dh=[15]) plogo.xaxis.visible = False plogo.yaxis.visible = False div1 = Div( text= """<font size=6><b><h>EuroLeague in a Spreadsheet</b></h></font></br></br><font size=5>With COVID-19 hitting sports hard - just as with everything else - spreadsheets finally get the position they deserve in sports. Euroleague was suspended before the regular season ended. Simulate the rest of the regular season and the playoffs to get your sports fix <br><br>Basketball Guru (@wiseballsguru), K. Pelechrinis (@kpelechrinis). </font><br></br></br><br></br></br>""", width=800, height=250) button = Button(label="Simulate", button_type="success", width=250) button.on_click(sim_callback) div2 = Div(text="""""", width=350) div3 = Div(text="""""", width=100) #layout = layout([[div1,],[button,],[table_standings,table_playoffs]]) layout = (column(row(div1, div3, plogo), row(button), row(table_standings, div2, table_playoffs))) curdoc().add_root(layout)
def login_signup(): w = WrapBokeh(PAGE_URL, logger) w.add( "tin_fname", TextInput(title="First Name:", placeholder="", css_classes=['tin_fname'])) w.add( "tin_lname", TextInput(title="Last Name:", placeholder="", css_classes=['tin_lname'])) w.add( "tin_uname", TextInput(title="User Name:", placeholder="", css_classes=['tin_uname'])) w.add( "tin_lpw", PasswordInput(title="Password:"******"", css_classes=['tin_lpw'])) w.add( "tin_lpw_confirm", PasswordInput(title="Confirm Password:"******"", css_classes=['tin_lpw_confirm'])) w.add("tin_email", TextInput(title="Email:", placeholder="", css_classes=['tin_email'])) w.add("b_submit", Button(label="Submit", css_classes=['b_submit'])) w.init() # Create a dominate document, see https://github.com/Knio/dominate # this line should go after any "return redirect" statements w.dominate_document() url_page_css(w.dom_doc, PAGE_URL) args, _redirect_page_metrics = w.process_req(request) if not args: return _redirect_page_metrics logger.info("{} : args {}".format(PAGE_URL, args)) redir, url = index_menu_redirect(args) if redir: return redirect(url) error_fields = {} submitted = args.get("b_submit", False) if submitted: validated, error_fields = User.validate(args) # on submit, validate form contents, show errors... if submitted and validated: logger.info("validated: {}".format(args)) User.add(first=args["tin_fname"], last=args["tin_lname"], username=args["tin_uname"], password=args["tin_lpw"], email=args["tin_email"]) return redirect(COMMON_URL_INDEX) doc_layout = layout(sizing_mode='scale_width') index_toolbar_menu(w, doc_layout, args) # show error fields... if any if submitted and not validated: for key, value in error_fields.items(): error = User.get_form_error_handle_from_err( key, value[0]) # process first error only w.add_css(key, error["css"]) w.get(key).title = error.get('msg', "!NO MSG!") w.add_css("tin_fname", {'input': {'width': '90%'}}) w.add_css("tin_lname", {'input': {'width': '90%'}}) w.add_css("tin_uname", {'input': {'width': '90%'}}) w.add_css("tin_lpw", {'input': {'width': '90%'}}) w.add_css("tin_lpw_confirm", {'input': {'width': '90%'}}) w.add_css("tin_email", {'input': {'width': '90%'}}) wbox = widgetbox(w.get("tin_fname"), w.get("tin_lname"), w.get("tin_uname"), w.get("tin_lpw"), w.get("tin_lpw_confirm"), w.get("tin_email"), w.get("b_submit")) left_margin = int(int(args.get("windowWidth", 800)) * 0.2) doc_layout.children.append(row([Spacer(width=left_margin), wbox])) return w.render(doc_layout)
plot.children[1] = column(create_plots(Estimators)) #lowecase innerscope variables variable1, variable2 = Variable1, Variable2 #Create the widgets drop1 = Select(title="Variable 1", options=list(X.columns.values), value=variable1) drop2 = Select(title="Variable 2", options=list(X.columns.values), value=variable2) drop3 = Select(title="variable 3", options=list(X.columns.values), value=y) estimator_names = [str(estimator).split("(")[0] for estimator in Estimators] estimator_indices = [str(i) for i, name in enumerate(estimator_names)] estimator_select = MultiSelect(title="Estimators", options=estimator_names, value=estimator_names[0:5]) button = Button(label="Update", button_type="success") button.on_click(update) plot = row(widgetbox(drop1, drop2, drop3, estimator_select, button), column(create_plots(Estimators))) curdoc().add_root(plot) curdoc().title = "Wage Data Visualisation"
def common__account_add(): # TODO: This needs to be a decorator if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN) user = User.get_by_id(session['user_id']) if user is None or not RolesUsers.user_has_role(user, ["ADMIN", "ADD-USER"]): # this should never happen... logout if it does... logger.error("Unable to find user id {}".format(session['user_id'])) session.pop('user_id', None) redirect(COMMON_URL_INDEX) w = WrapBokeh(PAGE_URL, logger) w.add( "tin_fname", TextInput(title="First Name:", placeholder="", css_classes=['tin_fname'])) w.add( "tin_lname", TextInput(title="Last Name:", placeholder="", css_classes=['tin_lname'])) w.add( "tin_uname", TextInput(title="User Name:", placeholder="", css_classes=['tin_uname'])) w.add( "tin_lpw", PasswordInput(title="Password:"******"", css_classes=['tin_lpw'])) w.add( "tin_lpw_confirm", PasswordInput(title="Confirm Password:"******"", css_classes=['tin_lpw_confirm'])) w.add("tin_email", TextInput(title="Email:", placeholder="", css_classes=['tin_email'])) w.add("b_submit", Button(label="Submit", css_classes=['b_submit'])) w.init() # Create a dominate document, see https://github.com/Knio/dominate # this line should go after any "return redirect" statements w.dominate_document() url_page_css(w.dom_doc, PAGE_URL) args, _redirect_page_metrics = w.process_req(request) if not args: return _redirect_page_metrics logger.info("{} : args {}".format(PAGE_URL, args)) redir, url = toolbar_menu_redirect(args) if redir: return redirect(url) error_fields = {} submitted = args.get("b_submit", False) if submitted: validated, error_fields = User.validate(args) # on submit, validate form contents, show errors... if submitted and validated: logger.info("validated: {}".format(args)) User.add(first=args["tin_fname"], last=args["tin_lname"], username=args["tin_uname"], password=args["tin_lpw"], email=args["tin_email"]) return redirect(COMMON_URL_ACCOUNT_ADD) doc_layout = layout(sizing_mode='scale_width') page_toolbar_menu(w, doc_layout, args, user) # show error fields... if any if submitted and not validated: for key, value in error_fields.items(): error = User.get_form_error_handle_from_err( key, value[0]) # process first error only w.add_css(key, error["css"]) w.get(key).title = error.get('msg', "!NO MSG!") w.add_css("tin_fname", {'input': {'width': '90%'}}) w.add_css("tin_lname", {'input': {'width': '90%'}}) w.add_css("tin_uname", {'input': {'width': '90%'}}) w.add_css("tin_lpw", {'input': {'width': '90%'}}) w.add_css("tin_lpw_confirm", {'input': {'width': '90%'}}) w.add_css("tin_email", {'input': {'width': '90%'}}) wbox = widgetbox(w.get("tin_fname"), w.get("tin_lname"), w.get("tin_uname"), w.get("tin_lpw"), w.get("tin_lpw_confirm"), w.get("tin_email"), w.get("b_submit")) left_margin = int(int(args.get("windowWidth", 800)) * 0.2) doc_layout.children.append(row([Spacer(width=left_margin), wbox])) return w.render(doc_layout)
def _init_flags_control_table(self): ''' Reminder: self.env.all_flags = { 2: 'FLAG 2', 3: 'FLAG 3', ... } ''' lg.info('-- INIT FLAGS CONTROL TABLE') # lg.info('-- ALL FLAGS DICTIONARY: {}'.format(self.env.all_flags)) for flag_index, str_value in self.env.all_flags.items(): def change_flag_vb(flag_index=flag_index): self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) vb_bt_to_change = self.env.doc.select_one( dict(name='flag_vb_bt_{}'.format(flag_index))) lg.info('>> CHANGING VISIBILITY: {}'.format( 'flag_vb_bt_{}'.format(flag_index))) new_visible_flags = self.env.visible_flags.copy() if 'eye_bt' in vb_bt_to_change.css_classes: new_visible_flags.remove(flag_index) vb_bt_to_change.css_classes = ['eye_slash_bt'] else: new_visible_flags.append(flag_index) vb_bt_to_change.css_classes = ['eye_bt'] self._update_visible_flags(new_visible_flags) self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) vb_bt = Button( name='flag_vb_bt_{}'.format(flag_index), label='', width=30, tags=['vb_bt'], css_classes=['eye_bt'], ) vb_bt.on_click(change_flag_vb) edit_flag_bt = self._init_edit_bt(flag_index) fg_str_div = Div(name='fg_str_div_{}'.format(flag_index), text='{}'.format(str_value), width=100, height=25, tags=['fg_str_div'], css_classes=['fg_str_div'], style={ 'color': CIRCLE_COLORS[flag_index], 'font-weight': 'bold', }) flag_row = row( name='flag_row_{}'.format(flag_index), children=[vb_bt, edit_flag_bt, fg_str_div], width=200, height=25, ) # self.env.flag_vb_bts.append(vb_bt) self.flag_rows.append(flag_row) self.env.flags_control_col = column( name='flags_control_col', children=[self.flags_control_header_row] + self.flag_rows, css_classes=['flags_control_col'], )
class BokehFlags(Environment): ''' Class to manage Flag Controls and its Events ''' env = Environment @property def all_flags_list(self): # NOTE: current available flags on the CDS plus flags with 0 elements return sorted([i for i, fg_str in self.env.all_flags.items()]) # TODO: If you want to assign a new flag value out of this list, # a mechanism to add a new value should be added def __init__(self, **kwargs): self.env.bk_flags = self self.env.visible_flags = self.all_flags_list self.all_flags_vb_bt = None self.flags_control_header_row = None self.flag_rows = [] self._init_flagger_select() self._init_flags_control_header() self._init_flags_control_table() def _init_flagger_select(self): lg.info('-- INIT FLAGGER SELECT') options = self.env.cruise_data.get_cols_by_type( ['param_flag', 'qc_param_flag']) options = sorted(options) self.env.flagger_select = Select( value=self.env.cur_flag, options=options, css_classes=['flagger_select'], ) def update_select_flag_value(attr, old, new): lg.info('-- SELECT VALUE | OLD: {} | NEW: {}'.format(old, new)) self.env.cur_flag = new if self.env.tab_change: self.env.tab_change = False else: lg.info('-- SELECT CHANGE') self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) self.env.tabs_flags_plots[self.env.cur_tab]['flag'] = new # TODO: replot of all the colors of the tab # some of the glyphs could be invisible # only an indices update is needed cur_plot_list = self.env.tabs_flags_plots[ self.env.cur_tab]['plots'] self.env.doc.hold('collect') self.env.bk_plots_handler.replot_color_circles( only_cur_tab=True) self.env.bk_sources._upd_prof_srcs( ) # TODO: keep the selection as it is >> keep_selection = True # I do this here because some point could be invisible for # other tab self.env.doc.unhold() self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) self.env.flagger_select.on_change('value', update_select_flag_value) def _init_flags_control_header(self): lg.info('-- FLAGS CONTROL HEADER') self.all_flags_vb_bt = Button( name='all_flags_bt', label='', width=30, css_classes=['eye_bt'], ) def all_flags_vb_bt_callback(): lg.info('-- ALL FLAGS VISIBLE CALLBACK') self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) all_flags_bt = self.env.doc.select_one(dict(name='all_flags_bt')) eye_slash_bt = True if 'eye_slash_bt' in all_flags_bt.css_classes else False if eye_slash_bt: self.env.doc.set_select(selector=dict(tags=['vb_bt']), updates=dict(css_classes=['eye_bt'])) else: self.env.doc.set_select( selector=dict(tags=['vb_bt']), updates=dict(css_classes=['eye_slash_bt'])) new_visible_flags = [] if 'eye_bt' in all_flags_bt.css_classes: all_flags_bt.css_classes = ['eye_slash_bt'] else: new_visible_flags = self.all_flags_list all_flags_bt.css_classes = ['eye_bt'] lg.info('>> NEW VISIBLE FLAGS: {}'.format(new_visible_flags)) self._update_visible_flags(new_visible_flags) self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) self.all_flags_vb_bt.on_click(all_flags_vb_bt_callback) # TODO: replace this div with the flag selection dropdown # or maybe there would be too many control on one place flag_controls_title_div = Div( name='flag_controls_title', text='All the flags', width=100, height=25, css_classes=['flag_controls_title'], ) self.flags_control_header_row = row( children=[self.all_flags_vb_bt, flag_controls_title_div], width=200, height=25, ) def _init_flags_control_table(self): ''' Reminder: self.env.all_flags = { 2: 'FLAG 2', 3: 'FLAG 3', ... } ''' lg.info('-- INIT FLAGS CONTROL TABLE') # lg.info('-- ALL FLAGS DICTIONARY: {}'.format(self.env.all_flags)) for flag_index, str_value in self.env.all_flags.items(): def change_flag_vb(flag_index=flag_index): self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) vb_bt_to_change = self.env.doc.select_one( dict(name='flag_vb_bt_{}'.format(flag_index))) lg.info('>> CHANGING VISIBILITY: {}'.format( 'flag_vb_bt_{}'.format(flag_index))) new_visible_flags = self.env.visible_flags.copy() if 'eye_bt' in vb_bt_to_change.css_classes: new_visible_flags.remove(flag_index) vb_bt_to_change.css_classes = ['eye_slash_bt'] else: new_visible_flags.append(flag_index) vb_bt_to_change.css_classes = ['eye_bt'] self._update_visible_flags(new_visible_flags) self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) vb_bt = Button( name='flag_vb_bt_{}'.format(flag_index), label='', width=30, tags=['vb_bt'], css_classes=['eye_bt'], ) vb_bt.on_click(change_flag_vb) edit_flag_bt = self._init_edit_bt(flag_index) fg_str_div = Div(name='fg_str_div_{}'.format(flag_index), text='{}'.format(str_value), width=100, height=25, tags=['fg_str_div'], css_classes=['fg_str_div'], style={ 'color': CIRCLE_COLORS[flag_index], 'font-weight': 'bold', }) flag_row = row( name='flag_row_{}'.format(flag_index), children=[vb_bt, edit_flag_bt, fg_str_div], width=200, height=25, ) # self.env.flag_vb_bts.append(vb_bt) self.flag_rows.append(flag_row) self.env.flags_control_col = column( name='flags_control_col', children=[self.flags_control_header_row] + self.flag_rows, css_classes=['flags_control_col'], ) def _init_edit_bt(self, flag_index): edit_flag_bt = Button(name='edit_flag_bt_{}'.format(flag_index), label='', width=30, tags=['edit_flag_bt'], css_classes=['edit_flag_bt']) def update_flag_value_edit_bt(flag_index=flag_index): self.update_flag_value(flag_value=flag_index, flag_to_update=None, row_indexes=self.env.selection) edit_flag_bt.on_click(update_flag_value_edit_bt) return edit_flag_bt def update_flag_value(self, flag_value=None, flag_to_update=None, row_indexes=[]): lg.info('-- UPDATE FLAG VALUE') self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_wait_cursor', }) if flag_value is None: lg.error( '>> An empty flag value got to `self.env.bk_flags.update_flag_value()`' ) if flag_to_update is None: flag_to_update = self.env.cur_flag if row_indexes == []: lg.error('>> NO row_indexes selected') self.env.cruise_data.update_flag_values( column=flag_to_update, new_flag_value=flag_value, row_indices=row_indexes, ) new_values = np.array(self.env.source.data[flag_to_update], dtype=int) # TODO: Int8 or Int64 new_values[row_indexes] = flag_value self.env.source.data[flag_to_update] = new_values self.env.bk_sources.cds_df[flag_to_update] = new_values # Updating flag colors self.env.doc.hold('collect') self.env.bk_plots_handler.replot_color_circles() # NOTE: update datatable and prof sources is needed because the new flag could be invisible, # then the profiles should be invisible as well # self.env.bk_table.update_dt_source() self.env.bk_sources._upd_prof_srcs(force_selection=True) self.env.doc.unhold() self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_default_cursor', }) self.env.bk_bridge.call_js({ 'object': 'tools', 'function': 'show_snackbar', 'params': [ '{} values of {} updated with the flag value {}'.format( len(row_indexes), flag_to_update, flag_value, ) ], }) def _update_visible_flags(self, to_visible_flags=[]): ''' Makes visible the flags passed as argument, and make invisible the rest @to_visible_flags: all the visible (or to make visible) flags indices ''' lg.info('-- UPDATE VISIBLE FLAGS') to_visible = [] to_invisible = [] for flag_index, flag_str in self.env.all_flags.items(): if flag_index in self.env.visible_flags and flag_index not in to_visible_flags: to_invisible.append('GR_FLAG_{}'.format(flag_index)) if flag_index not in self.env.visible_flags and flag_index in to_visible_flags: to_visible.append('GR_FLAG_{}'.format(flag_index)) # lg.info('>> TO VISIBLE FLAGS: {}'.format(to_visible_flags)) # lg.info('>> TO VISIBLE: {}'.format(to_visible)) # lg.info('>> TO INVISIBLE: {}'.format(to_invisible)) self.env.doc.hold('collect') if to_visible != []: self.env.doc.set_select(selector=dict(tags=to_visible), updates=dict(visible=True)) if to_invisible != []: self.env.doc.set_select(selector=dict(tags=to_invisible), updates=dict(visible=False)) all_flags_bt = self.env.doc.select_one(dict(name='all_flags_bt')) if to_visible_flags == []: all_flags_bt.css_classes = ['eye_slash_bt'] else: all_flags_bt.css_classes = ['eye_bt'] self.env.visible_flags = to_visible_flags.copy() self.env.bk_sources._upd_prof_srcs(force_selection=True) self.env.doc.unhold() def reset_all_flags(self): lg.info('-- RESET ALL FLAGS') if sorted(self.env.visible_flags) != self.all_flags_list: self.env.doc.set_select(selector=dict(tags=['vb_bt']), updates=dict(css_classes=['eye_bt'])) to_visible = [] for flag_index, flag_str in self.env.all_flags.items(): if flag_index not in self.env.visible_flags: to_visible.append('GR_FLAG_{}'.format(flag_index)) self.env.doc.set_select(selector=dict(tags=to_visible), updates=dict(visible=True)) self.env.visible_flags = self.all_flags_list all_flags_bt = self.env.doc.select_one(dict(name='all_flags_bt')) all_flags_bt.css_classes = ['eye_bt']
def roles_edit(): logger = logging.getLogger("TMI.roles_edit") def pop_session(): session.pop("roles_initial", None) session.pop("roles", None) # TODO: This needs to be a decorator if not session.get('user_id', False): return redirect(COMMON_URL_LOGIN) user = User.get_by_id(session['user_id']) if user is None or not RolesUsers.user_has_role(user, ["EDIT-ROLE", "ADD-USER"]): # this should never happen... logout if it does... logger.error("Unable to find user id {}".format(session['user_id'])) session.pop('user_id', None) redirect(COMMON_URL_INDEX) w = WrapBokeh(PAGE_URL, logger) w.add( "sel_uname", Select(options=[], value=None, title="Select User", css_classes=['sel_uname'])) w.add("cbg_roles", CheckboxGroup(labels=[], active=[], css_classes=['cbg_roles'])) w.add("b_submit", Button(label="Update", css_classes=['b_submit'])) w.add("b_cancel", Button(label="Cancel", css_classes=['b_cancel'])) w.init() user = User.get_by_id(session['user_id']) if user is None: logger.error("Unable to find user id {}".format(session['user_id'])) session.pop('user_id') redirect(COMMON_URL_INDEX) # Create a dominate document, see https://github.com/Knio/dominate # this line should go after any "return redirect" statements w.dominate_document() url_page_css(w.dom_doc, PAGE_URL) args, _redirect_page_metrics = w.process_req(request) if not args: return _redirect_page_metrics logger.info("{} : args {}".format(PAGE_URL, args)) redir, url = toolbar_menu_redirect(args) if redir: pop_session() return redirect(url) if args.get("b_cancel", False): pop_session() return redirect(COMMON_URL_LAND) updated = False if args.get( "b_submit", False) and session["roles_initial"] != w.get("cbg_roles").active: # need to convert the CheckboxGroup list indexes to Role IDs selected_idexes = w.get("cbg_roles").active if selected_idexes == [None]: selected_idexes = [] selected_roles = [] for idx in selected_idexes: selected_roles.append(session["roles"][idx]) edit_user = User.get_username(w.get("sel_uname").value) logger.info("{} updated roles {}".format(edit_user.username, selected_roles)) success = User.update_roles(edit_user.username, selected_roles) if success: updated = True doc_layout = layout(sizing_mode="fixed") page_toolbar_menu(w, doc_layout, args, user) # populate users all_users = User.get_username(None, all=True) sel_users = [("Select", "Select User")] for u in all_users: if u.username in app.config["app"]["user"]["protected"]: continue sel_users.append((u.username, u.username)) w.get("sel_uname").options = sel_users w.get("sel_uname").value = args.get("sel_uname", None) # last value or none session["roles_initial"] = [] # new selection was done, update the roles if w.get("sel_uname").value not in ['Select', None]: edit_user = User.get_username(w.get("sel_uname").value) roles = [] user_ids = [] session["roles"] = [] for _id, _name, _desc in Role.get_all(): session["roles"].append(_name) roles.append(_desc) if RolesUsers.user_has_role(edit_user, _name): user_ids.append(roles.index(_desc)) w.get("cbg_roles").labels = roles w.get("cbg_roles").active = user_ids if args['callerWidget'] == 'sel_uname' or updated: session["roles_initial"] = w.get("cbg_roles").active if args["callerWidget"] == 'cbg_roles': if len(args['cbg_roles']) == 0: w.get("cbg_roles").active = [] else: w.get("cbg_roles").active = [ int(i) for i in args['cbg_roles'].split(",") ] # change submit button if there is a change in roles if session["roles_initial"] == w.get("cbg_roles").active: w.add_css( "b_submit", { 'button': { 'background-color': GUI.BUTTON_DISABLED_GRAY, 'pointer-events': None } }) else: w.add_css("b_submit", {'button': { 'background-color': GUI.BUTTON_ENABLED_GREEN }}) w.add_css("b_cancel", {'button': {'background-color': GUI.BUTTON_CANCEL}}) wbox = widgetbox(w.get("sel_uname"), w.get("cbg_roles"), w.get("b_submit"), w.get("b_cancel")) left_margin = int(int(args.get("windowWidth", 800)) * 0.2) doc_layout.children.append(row([Spacer(width=left_margin), wbox])) return w.render(doc_layout)
def visualize(self): """ Generates a plot using bokeh, which displays the initial trajectory and the optimized trajectory of the cutting tool. """ # Tools that will be displayed on the plots tools = "pan,wheel_zoom,reset,save" # Plot displaying the optimized path result_plot = figure(plot_width=1000, plot_height=500, tools=tools, active_scroll='wheel_zoom') result_plot.title.text = "Optimized Path" # Plot displaying the non optimized path initial_plot = figure(plot_width=1000, plot_height=500, tools=tools, active_scroll='wheel_zoom') initial_plot.title.text = "Initial Path" # Add the data to the result plot result_plot = self.populate_plot(result_plot, self.result) result_plot.legend.location = "bottom_right" # Add the data to the initial plot initial_plot = self.populate_plot(initial_plot, self.initial) initial_plot.legend.location = "bottom_right" # Add cutting tool to plots # Generate the points on which the triangle should move on result_lines_x, result_lines_y = self.generate_tool_path( self.result, 1) initial_lines_x, initial_lines_y = self.generate_tool_path( self.initial, 1) # Add cutting tool triangle to optimized path result_triangle_position = ColumnDataSource( data=dict(x=[result_lines_x[0]], y=[result_lines_y[0]])) result_triangle = Triangle(x='x', y='y', line_color=Category10_4[3], line_width=3, size=20, fill_alpha=0) result_plot.add_glyph(result_triangle_position, result_triangle) # Add cutting tool triangle to initial path initial_triangle_position = ColumnDataSource( data=dict(x=[initial_lines_x[0]], y=[initial_lines_y[0]])) initial_triangle = Triangle(x='x', y='y', line_color=Category10_4[3], line_width=3, size=20, fill_alpha=0) initial_plot.add_glyph(initial_triangle_position, initial_triangle) # Add button to start moving the triangle button = Button(label='Start') result_num_steps = result_lines_x.shape[0] initial_num_steps = initial_lines_x.shape[0] num_steps = max(result_num_steps, initial_num_steps) # JavaScript callback which will be called once the button is pressed callback = CustomJS(args=dict( result_triangle_position=result_triangle_position, result_lines_x=result_lines_x, result_lines_y=result_lines_y, result_num_steps=result_num_steps, initial_triangle_position=initial_triangle_position, initial_lines_x=initial_lines_x, initial_lines_y=initial_lines_y, initial_num_steps=initial_num_steps, num_steps=num_steps), code=""" // Animate optimal path plot for(let i = 0; i < num_steps; i += 50) { setTimeout(function() { if (i < result_num_steps) { result_triangle_position.data['x'][0] = result_lines_x[i] result_triangle_position.data['y'][0] = result_lines_y[i] } if (i < initial_num_steps) { initial_triangle_position.data['x'][0] = initial_lines_x[i] initial_triangle_position.data['y'][0] = initial_lines_y[i] } result_triangle_position.change.emit() initial_triangle_position.change.emit() }, i) } """) # Add callback function to button, which starts the whole animation button.js_on_click(callback) # Save the plot result_plot = row([result_plot, button]) plot = column([result_plot, initial_plot]) output_file("visualization.html", title="CNC Path Optimization") save(plot)
def common__login(): w = WrapBokeh(PAGE_URL, logger) w.add( "tin_uname", TextInput(title="Login Name:", placeholder="", css_classes=['tin_lname'])) w.add( "tin_lpw", PasswordInput(title="Password:"******"", css_classes=['tin_lpw'])) w.add("b_submit", Button(label="Submit", css_classes=['b_submit'])) w.add("b_signup", Button(label="Sign Up", css_classes=['b_signup'])) w.add("b_recover", Button(label="Recover Password", css_classes=['b_recover'])) w.init() # Create a dominate document, see https://github.com/Knio/dominate # this line should go after any "return redirect" statements w.dominate_document(title=session["title"]) url_page_css(w.dom_doc, PAGE_URL) args, _redirect_page_metrics = w.process_req(request) if not args: return _redirect_page_metrics logger.info("{} : args {}".format(PAGE_URL, args)) left_margin = int(int(args.get("windowWidth", 800)) * 0.1) redir, url = index_menu_redirect(args) if redir: return redirect(url) if args.get("b_signup", False): return redirect(COMMON_URL_LOGIN_SIGNUP) if args.get("b_recover", False): return redirect(COMMON_URL_LOGIN_RECOVER) login_failed = False if args.get("b_submit", False): uname = args.get("tin_uname", None) pw = w.get("tin_lpw").value if uname is not None and pw is not None: user = User.login(uname, pw) if user is not None: logger.info("{} {}".format(user.username, user.id)) session['user_id'] = user.id login(user.id) return redirect(COMMON_URL_LAND) else: logger.info("Login failed for {}".format(uname)) login_failed = True doc_layout = layout(sizing_mode='scale_width') index_toolbar_menu(w, doc_layout, args) if login_failed: doc_layout.children.append( row( Spacer(width=left_margin), column([ Div(text="""<p>Login failed, Recover Password?</p>"""), w.get("b_recover") ]))) w.add_css("b_submit", {'button': { 'background-color': '#98FB98', 'min-width': '60px' }}) w.add_css("b_signup", {'button': { 'background-color': '#98FB98', 'min-width': '60px' }}) w.add_css("tin_uname", {'input': {'width': '90%'}}) w.add_css("tin_lpw", {'input': {'width': '90%'}}) if app.config["app"]["user"]["signup_enabled"]: wbox = widgetbox(w.get("tin_uname"), w.get("tin_lpw"), w.get("b_submit"), w.get("b_signup")) else: wbox = widgetbox(w.get("tin_uname"), w.get("tin_lpw"), w.get("b_submit")) doc_layout.children.append(row([Spacer(width=left_margin), wbox])) return w.render(doc_layout)
def plot(self, pm: PrettyMIDI): """ Plots the pretty midi object as a plot object. :param pm: the PrettyMIDI instance to plot :return: the bokeh plot layout """ preset = self._preset # Calculates the QPM from the MIDI file, might raise exception if confused qpm = self._get_qpm(pm) # Initialize the tools, those are present on the right hand side plot = bokeh.plotting.figure(tools="reset,hover,save,wheel_zoom,pan", toolbar_location=preset.toolbar_location) # Setup the hover and the data dict for bokeh, # each property must match a property in the data dict plot.select(dict(type=bokeh.models.HoverTool)).tooltips = ({ "program": "@program", "pitch": "@top", "velocity": "@velocity", "duration": "@duration", "start_time": "@left", "end_time": "@right" }) data = dict(program=[], top=[], bottom=[], left=[], right=[], duration=[], velocity=[], color=[]) # Puts the notes in the dict for bokeh and saves first # and last note time, bigger and smaller pitch pitch_min = None pitch_max = None first_note_start = None last_note_end = None index_instrument = 0 for instrument in pm.instruments: for note in instrument.notes: pitch_min = min(pitch_min or self._MAX_PITCH, note.pitch) pitch_max = max(pitch_max or self._MIN_PITCH, note.pitch) color = self._get_color(index_instrument, note) note_start = note.start note_end = note.start + (note.end - note.start) data["program"].append(instrument.program) data["top"].append(note.pitch) if self._show_velocity: data["bottom"].append(note.pitch + (note.velocity / 127)) else: data["bottom"].append(note.pitch + 1) data["left"].append(note_start) data["right"].append(note_end) data["duration"].append(note_end - note_start) data["velocity"].append(note.velocity) data["color"].append(color) first_note_start = min(first_note_start or sys.maxsize, note_start) last_note_end = max(last_note_end or 0, note_end) index_instrument = index_instrument + 1 # Shows an empty plot even if there are no notes if first_note_start is None or last_note_end is None or pitch_min is None or pitch_max is None: pitch_min = self._MIN_PITCH pitch_max = pitch_min + 5 first_note_start = 0 last_note_end = 0 # Gets the pitch range (min, max) from either the provided arguments # or min and max values from the notes if self._plot_pitch_range_start is not None: pitch_min = self._plot_pitch_range_start else: pitch_min = min(self._MAX_PITCH, pitch_min) if self._plot_pitch_range_stop is not None: pitch_max = self._plot_pitch_range_stop else: pitch_max = max(self._MIN_PITCH, pitch_max) pitch_range = pitch_max + 1 - pitch_min # Draws the rectangles on the plot from the data source = ColumnDataSource(data=data) plot.quad(left="left", right="right", top="top", bottom="bottom", line_alpha=1, line_color="black", color="color", source=source) # Draws the y grid by hand, because the grid has label on the ticks, but # for a plot like this, the labels needs to fit in between the ticks. # Also useful to change the background of the grid each line for pitch in range(pitch_min, pitch_max + 1): # Draws the background box and contours, on the underlay layer, so # that the rectangles and over the box annotations fill_alpha = (0.15 if pitch % 2 == 0 else 0.00) box = BoxAnnotation(bottom=pitch, top=pitch + 1, fill_color="gray", fill_alpha=fill_alpha, line_color="black", line_alpha=0.3, line_width=1, level="underlay") plot.add_layout(box) label = Label(x=preset.label_y_axis_offset_x, y=pitch + preset.label_y_axis_offset_y, x_units="screen", text=str(pitch), render_mode="css", text_font_size=preset.label_text_font_size, text_font_style=preset.label_text_font_style) plot.add_layout(label) # Gets the time signature from pretty midi, or 4/4 if none if self._midi_time_signature: numerator, denominator = self._midi_time_signature.split("/") time_signature = TimeSignature(int(numerator), int(denominator), 0) else: if pm.time_signature_changes: if len(pm.time_signature_changes) > 1: raise Exception( "Multiple time signatures are not supported") time_signature = pm.time_signature_changes[0] else: time_signature = TimeSignature(4, 4, 0) # Gets seconds per bar and seconds per beat if len(pm.get_beats()) >= 2: seconds_per_beat = pm.get_beats()[1] - pm.get_beats()[0] else: seconds_per_beat = 0.5 if len(pm.get_downbeats()) >= 2: seconds_per_bar = pm.get_downbeats()[1] - pm.get_downbeats()[0] else: seconds_per_bar = 2.0 # Defines the end time of the plot in seconds if self._plot_bar_range_stop is not None: plot_end_time = self._plot_bar_range_stop * seconds_per_bar else: # Calculates the plot start and end time, the start time can start after # notes or truncate notes if the plot is too long (we left truncate the # plot with the bounds) # The plot start and plot end are a multiple of seconds per bar (closest # smaller value for the start time, closest higher value for the end time) plot_end_time = int( (last_note_end) / seconds_per_bar) * seconds_per_bar # If the last note end is exactly on a multiple of seconds per bar, # we don't start a new one is_on_bar = math.isclose(last_note_end % seconds_per_bar, seconds_per_bar) is_on_bar_exact = math.isclose(last_note_end % seconds_per_bar, 0.0) if not is_on_bar and not is_on_bar_exact: plot_end_time += seconds_per_bar # Defines the start time of the plot in seconds if self._plot_bar_range_start is not None: plot_start_time = self._plot_bar_range_start * seconds_per_bar else: start_time = int( first_note_start / seconds_per_bar) * seconds_per_bar plot_max_length_time = self._plot_max_length_bar * seconds_per_bar plot_start_time = max(plot_end_time - plot_max_length_time, start_time) # Draws the vertical bar grid, with a different background color # for each bar if preset.show_bar: bar_count = 0 for bar_time in pm.get_downbeats(): fill_alpha_index = bar_count % len(self._bar_fill_alphas) fill_alpha = self._bar_fill_alphas[fill_alpha_index] box = BoxAnnotation(left=bar_time, right=bar_time + seconds_per_bar, fill_color="gray", fill_alpha=fill_alpha, line_color="black", line_width=2, line_alpha=0.5, level="underlay") plot.add_layout(box) bar_count += 1 # Draws the vertical beat grid, those are only grid lines if preset.show_beat: for beat_time in pm.get_beats(): box = BoxAnnotation(left=beat_time, right=beat_time + seconds_per_beat, fill_color=None, line_color="black", line_width=1, line_alpha=0.4, level="underlay") plot.add_layout(box) # Configure x axis plot.xaxis.bounds = (plot_start_time, plot_end_time) plot.xaxis.axis_label = "time (SEC)" plot.xaxis.axis_label_text_font_size = preset.axis_label_text_font_size plot.xaxis.ticker = bokeh.models.SingleIntervalTicker(interval=1) plot.xaxis.major_tick_line_alpha = 0.9 plot.xaxis.major_tick_line_width = 1 plot.xaxis.major_tick_out = preset.axis_x_major_tick_out plot.xaxis.minor_tick_line_alpha = 0 plot.xaxis.major_label_text_font_size = preset.label_text_font_size plot.xaxis.major_label_text_font_style = preset.label_text_font_style # Configure y axis plot.yaxis.bounds = (pitch_min, pitch_max + 1) plot.yaxis.axis_label = "pitch (MIDI)" plot.yaxis.axis_label_text_font_size = preset.axis_label_text_font_size plot.yaxis.ticker = bokeh.models.SingleIntervalTicker(interval=1) plot.yaxis.major_label_text_alpha = 0 plot.yaxis.major_tick_line_alpha = 0.9 plot.yaxis.major_tick_line_width = 1 plot.yaxis.major_tick_out = preset.axis_y_major_tick_out plot.yaxis.minor_tick_line_alpha = 0 plot.yaxis.axis_label_standoff = preset.axis_y_label_standoff plot.outline_line_width = 1 plot.outline_line_alpha = 1 plot.outline_line_color = "black" # The x grid is deactivated because is draw by hand (see x grid code) plot.xgrid.grid_line_color = None # The y grid is deactivated because is draw by hand (see y grid code) plot.ygrid.grid_line_color = None # Configure the plot size and range plot_title_text = "Visual MIDI (%s QPM, %s/%s)" % (str( int(qpm)), time_signature.numerator, time_signature.denominator) plot.title = Title(text=plot_title_text, text_font_size=preset.title_text_font_size) plot.plot_width = preset.plot_width if preset.row_height: plot.plot_height = pitch_range * preset.row_height else: plot.plot_height = preset.plot_height plot.x_range = Range1d(plot_start_time, plot_end_time) plot.y_range = Range1d(pitch_min, pitch_max + 1) plot.min_border_right = 50 if self._live_reload and preset.stop_live_reload_button: callback = CustomJS(code="clearInterval(liveReloadInterval)") button = Button(label="stop live reload") button.js_on_click(callback) layout = column(button, plot) else: layout = column(plot) return layout
def login_recover(): w = WrapBokeh(PAGE_URL, logger) w.add( "tin_uname_only", TextInput(title="User Name:", placeholder="", css_classes=['tin_uname_only'])) w.add( "tin_email_only", TextInput(title="Email:", placeholder="", css_classes=['tin_email_only'])) w.add("b_submit", Button(label="Submit", css_classes=['b_submit'])) w.add("b_ok", Button(label="Ok", css_classes=['b_ok'])) w.init() # Create a dominate document, see https://github.com/Knio/dominate # this line should go after any "return redirect" statements w.dominate_document() url_page_css(w.dom_doc, PAGE_URL) args, _redirect_page_metrics = w.process_req(request) if not args: return _redirect_page_metrics logger.info("{} : args {}".format(PAGE_URL, args)) left_margin = int(int(args.get("windowWidth", 800)) * 0.2) if args.get("b_ok", False): return redirect(COMMON_URL_INDEX) redir, url = index_menu_redirect(args) if redir: return redirect(url) failed_credentials_match = False recovery_email_sent = False error_fields = {} submitted = args.get("b_submit", False) if submitted: validated, error_fields = User.validate(args) if validated: # uname, email format is valid, now lets see if it exists user = User.get_username(args.get("tin_uname_only")) if user in [None, []] or user.email != args.get("tin_email_only"): logger.error( "Invalid username/pw ({}/{}) for login recovery".format( user.username, args.get("tin_email_only"))) failed_credentials_match = True if not failed_credentials_match: logger.info("user validated, sending recovery email") temp_pw = "12345" # FIXME: make a random password # TODO: send email User.update(original_username=user.username, first=user.fname, last=user.lname, username=user.username, password=temp_pw, email=user.email) recovery_email_sent = True doc_layout = layout(sizing_mode='scale_width') index_toolbar_menu(w, doc_layout, args) doc_layout = layout(sizing_mode='scale_width') # show error fields... if any if submitted and not validated: for key, value in error_fields.items(): error = User.get_form_error_handle_from_err( key, value[0]) # process first error only w.add_css(key, error["css"]) w.get(key).title = error.get('msg', "!NO MSG!") if recovery_email_sent: doc_layout.children.append( row([ Spacer(width=left_margin), Div(text="""<h1>An Email has been sent!</h1>""") ])) doc_layout.children.append( row([Spacer(width=left_margin), w.get("b_ok")])) elif failed_credentials_match: doc_layout.children.append( row([ Spacer(width=left_margin), Div(text= """<h1>Those credentials did not match a known user.</h1>""", height=150) ])) doc_layout.children.append( row([Spacer(width=left_margin), w.get("b_ok")])) else: w.add_css("tin_uname_only", {'input': {'width': '90%'}}) w.add_css("tin_email_only", {'input': {'width': '90%'}}) wbox = widgetbox(w.get("tin_uname_only"), w.get("tin_email_only"), w.get("b_submit")) doc_layout.children.append(row([Spacer(width=left_margin), wbox])) return w.render(doc_layout)
class BokehEvents(Environment): ''' Controls and events. The widgets and buttons are created here Also some other events and triggers such as update points selections ''' env = Environment def __init__(self, **kwargs): lg.info('-- INIT BOKEH EVENTS') self.env.bk_events = self self.env.source.selected.on_change('indices', self._update_selection) self.env.wmts_map_source.selected.on_change('indices', self._update_map_selection) self._init_cb_prof_invsbl_points() self._init_profile_nav() self._init_nearby_prof_cb() self._init_tabs() def _update_selection(self, attr, old, new_indices): ''' This is run when some elements are selected: @attr: 'selected' @old: >> does not work well yet with the new Selection class? @new: >> new_indices store the new selection indices list ''' lg.info('-- UPDATE SELECTION: {}'.format(new_indices)) self.env.dt_manual_update = False if self.env.selection != new_indices and new_indices != []: self.env.selection = new_indices self.env.sample_to_select = None # this is reselected later on `_upd_prof_srcs` self.env.stt_to_select = None self.env.cur_nearby_prof = None self.env.cur_partial_stt_selection = [] self._update_map_selection_prog(new_indices) self.env.bk_table.update_dt_source( ) # prof sources is updated inside self.env.reset_selection = False elif self.env.selection != [] and new_indices == []: # NOTE: Keeps the selection when the user click on a space without any sample if self.env.reset_selection: lg.info('>> RESET SELECTION') self.env.selection = [] self.env.sample_to_select = None # this is reselected later on `_upd_prof_srcs` self.env.stt_to_select = None self.env.cur_nearby_prof = None self.env.cur_partial_stt_selection = [] self.env.reset_selection = False else: lg.info('>> KEEP SELECTION') self.env.source.selected.indices = self.env.selection # keep selection self.env.dt_manual_update = True # reactivate the manual update of the datatable def _update_map_selection(self, attr, old, new_indices): lg.info('-- UPDATE MAP SELECTION') if self.env.map_selection != new_indices and new_indices != []: # this can be triggered by the user, and programmatically?? lg.info('>> NEW MAP SELECTION: {}'.format(new_indices)) self.env.map_selection = new_indices # triggers the _update_selection # lg.info('>> MAP SOURCE: {}'.format(self.env.wmts_map_source.data)) selected_stts = list( self.env.wmts_map_source.data[STNNBR][new_indices]) # self.env.cur_partial_stt_selection = selected_stts # TODO: how to get all the indices of the current selected stations?? by self.env.cds_df is updated?? sel_inds = list(self.env.cds_df[self.env.cds_df[STNNBR].isin( selected_stts)].index) self.env.source.selected.indices = sel_inds self._update_selection( attr='selected', old=None, new_indices=self.env.source.selected. indices # I need to trigger this manually as well ) elif self.env.map_selection != [] and new_indices == []: if self.env.reset_selection: lg.info('>> RESET MAP SELECTION') self.env.map_selection = [] self.env.wmts_map_source.selected.indices = [] else: lg.info('>> KEEP MAP SELECTION') self.env.wmts_map_source.selected.indices = self.env.map_selection def _update_map_selection_prog(self, new_indices): ''' Updates the map selection programmatically with the stations of the selected samples @new_indices: list of indices of the selected samples (cds_df) ''' lg.info('-- UPDATE MAP SELECTION PROGRAMMATICALLY') if self.env.map_selection != new_indices and new_indices != []: # this can be triggered by the user, and programmatically?? stt_to_select = list(self.env.cds_df.loc[new_indices, STNNBR]) indices_to_select = list(self.env.wmts_map_df[ self.env.wmts_map_df[STNNBR].isin(stt_to_select)].index) self.env.map_selection = indices_to_select self.env.wmts_map_source.selected.indices = indices_to_select elif self.env.map_selection != [] and new_indices == []: if self.env.reset_selection: lg.info('>> RESET MAP SELECTION') self.env.map_selection = [] self.env.wmts_map_source.selected.indices = [] def _init_cb_prof_invsbl_points(self): ''' Plot the profiles with the visible points or with the invisible as well ''' def on_click_cb_prof_invsbl_points(active_list): if active_list == [0]: self.env.plot_prof_invsbl_points = True else: self.env.plot_prof_invsbl_points = False self.env.bk_sources._upd_prof_srcs() self.cb_prof_invsbl_points = CheckboxGroup( width=200, height=10, labels=['Fixed profiles'], # Plot invisible points on profiles active=[], css_classes=['fixed_profiles_cb', 'bokeh_hidden']) self.cb_prof_invsbl_points.on_click(on_click_cb_prof_invsbl_points) def _init_profile_nav(self): def next_profile(): if self.nearby_prof_cb: lg.info('-- NEXT PROFILE') s = self.env.stations next_pos = s.index(self.env.cur_nearby_prof) + 1 if next_pos < len(self.env.stations): if s[next_pos] == self.env.stt_to_select: next_pos = next_pos + 1 if next_pos < len(self.env.stations): self.env.cur_nearby_prof = s[next_pos] self.env.bk_sources._upd_prof_srcs(force_selection=True) self.nearby_prof_div.text = str( int(self.env.cur_nearby_prof)) # adjust disabled buttons if next_pos + 1 == len(self.env.stations): self.next_prof_bt.disabled = True self.previous_prof_bt.disabled = False def previous_profile(): lg.info('-- PREVIOUS PROFILE') if self.nearby_prof_cb: s = self.env.stations previous_pos = s.index(self.env.cur_nearby_prof) - 1 if previous_pos >= 0: if s[previous_pos] == self.env.stt_to_select: previous_pos = previous_pos - 1 if previous_pos >= 0: self.env.cur_nearby_prof = s[previous_pos] self.env.bk_sources._upd_prof_srcs(force_selection=True) self.nearby_prof_div.text = str( int(self.env.cur_nearby_prof)) # adjust disabled buttons if previous_pos == 0: self.previous_prof_bt.disabled = True self.next_prof_bt.disabled = False self.next_prof_bt = Button( width=30, disabled=True, label=">", button_type="success", ) self.nearby_prof_div = Div( width=100, height=30, text='None', css_classes=['cur_stt'], ) self.previous_prof_bt = Button( width=30, disabled=True, label="<", button_type="success", ) self.next_prof_bt.on_click(next_profile) self.previous_prof_bt.on_click(previous_profile) def _init_nearby_prof_cb(self): def on_click_nearby_prof(active_list): lg.info('-- ONCLICK NEARBY PROF') lg.info('>> SELECTED STT: {}'.format(self.env.stt_to_select)) if 0 in active_list: self.env.plot_nearby_prof = True self.set_cur_nearby_prof() self.env.bk_sources._upd_prof_srcs(force_selection=True) else: self.env.plot_nearby_prof = False self.next_prof_bt.disabled = True self.previous_prof_bt.disabled = True self.env.cur_nearby_prof = None self.nearby_prof_div.text = 'None' self.env.bk_sources._upd_prof_srcs(force_selection=True) self.nearby_prof_cb = CheckboxGroup( width=200, height=20, labels=['Show nearby station'], active=[], css_classes=['show_nearby_station_cb', 'bokeh_hidden']) self.nearby_prof_cb.on_click(on_click_nearby_prof) def set_cur_nearby_prof(self): lg.info('-- SET CUR NEARBY PROF') self.next_prof_bt.disabled = False # TODO: if the database has only one station? self.previous_prof_bt.disabled = False # NOTE: get the default extra station: the next one if exists # if not, the previous one if self.env.stt_to_select is not None: next_pos = self.env.stations.index(self.env.stt_to_select) + 1 if next_pos < len(self.env.stations): self.env.cur_nearby_prof = self.env.stations[next_pos] self.nearby_prof_div.text = str(int(self.env.cur_nearby_prof)) else: previous_pos = self.env.stations.index( self.env.stt_to_select) - 1 if previous_pos >= 0: self.env.cur_nearby_prof = self.env.stations[previous_pos] self.nearby_prof_div.text = str( int(self.env.cur_nearby_prof)) def _init_tabs(self): lg.info('-- INIT TABS') panel_list = [] # lg.info('>> self.env.TABS_FLAGS_PLOTS: {}'.format(self.env.tabs_flags_plots)) SORT_TABS = False if SORT_TABS: ordered_tab_list = sorted(self.env.tabs_flags_plots) else: ordered_tab_list = list(self.env.tabs_flags_plots.keys()) self.env.cur_tab = ordered_tab_list[ 0] # self.env.cur_tab initialization self.env.cur_flag = self.env.cur_tab + FLAG_END # self.env.cur_tab initialization ly_settings = self.env.f_handler.get_layout_settings() for tab in ordered_tab_list: indices = self.env.tabs_flags_plots[tab]['plots'] children = [ x.plot for x in self.env.bk_plots if x.n_plot in indices ] # lg.info('>> CHILDREN: {}'.format(children)) gp = gridplot( children=children, ncols=ly_settings['ncols'], plot_width=ly_settings[ 'plot_width'], # if 350 then the points are blurred plot_height=ly_settings['plot_height'], toolbar_location= 'left', # TODO: separate the toolbars to set some tools active by default, # like this the hover icon can be shown as well ) name = 'panel_{}'.format(tab.lower()) panel_list.append( Panel( name='panel_{}'.format(tab.lower()), child=gp, title=tab, )) # TODO: closable=True lg.info('>> TABS WIDGET: {}'.format(self.env.tabs_widget)) if self.env.tabs_widget is None: self.env.tabs_widget = Tabs( name='tabs_widget', tabs=panel_list, width=1250, ) else: self.env.tabs_widget.tabs.clear() self.env.tabs_widget.tabs = panel_list def update_active_tab(attr, old, new): lg.info('-- UPDATE ACTIVE TAB | OLD: {} | NEW: {}'.format( old, new)) self.env.cur_tab = self.env.tabs_widget.tabs[new].title lg.info('>> CUR TAB: {}'.format(self.env.cur_tab)) flag = self.env.tabs_flags_plots[self.env.cur_tab]['flag'] if self.env.flagger_select.value != flag: self.env.tab_change = True self.env.flagger_select.value = flag # if they concide the update of the select is not triggered self.env.tabs_widget.on_change('active', update_active_tab)
def _getPlotPanel(self): """@brief Add tab that shows plot data updates.""" self._figTable.append([]) self._voltsSource = ColumnDataSource({'x': [], 'y': []}) self._ampsSource = ColumnDataSource({'x': [], 'y': []}) self._wattsSource = ColumnDataSource({'x': [], 'y': []}) fig = figure(toolbar_location='above', x_axis_type="datetime", x_axis_location="below") fig.line(source=self._voltsSource, line_color="blue", legend_label="Volts") fig.line(source=self._ampsSource, line_color="green", legend_label="Amps") fig.line(source=self._wattsSource, line_color="red", legend_label="Watts") fig.legend.location = 'top_left' self._figTable[-1].append(fig) self._grid = gridplot(children=self._figTable, sizing_mode='scale_both', toolbar_location='right') self.selectSerialPort = Select(title="Serial Port:") self.selectSerialPort.options = glob.glob('/dev/ttyU*') self.outputVoltageSpinner = Spinner(title="Output Voltage (Volts)", low=0, high=40, step=0.5, value=self._pconfig.getAttr( PSUGUI.VOLTS)) self.currentLimitSpinner = Spinner(title="Currnet Limit (Amps)", low=0, high=10, step=0.25, value=self._pconfig.getAttr( PSUGUI.AMPS)) self.plotHistorySpinner = Spinner(title="Plot History (Seconds)", low=1, high=10000, step=1, value=self._pconfig.getAttr( PSUGUI.PLOT_SECONDS)) self._setButton = Button(label="Set") self._setButton.on_click(self._setHandler) self._setButton.disabled = True self._onButton = Button(label="On") self._onButton.on_click(self._psuOnHandler) shutdownButtonWrapper = ShutdownButtonWrapper(self._quit) controlPanel = column([ self.selectSerialPort, self._onButton, self.outputVoltageSpinner, self.currentLimitSpinner, self._setButton, self.plotHistorySpinner, shutdownButtonWrapper.getWidget() ]) self._opTableWrapper = ReadOnlyTableWrapper(("volts", "amps", "watts"), heightPolicy="fixed", height=65, showLastRows=0) plotPanel = column([self._grid, self._opTableWrapper.getWidget()]) panel2 = row([controlPanel, plotPanel]) plotPanel = column([panel2, self.statusBarWrapper.getWidget()]) return plotPanel
class BokehDataTable(Environment): ''' DataTable Controls and events ''' env = Environment def __init__(self, **kwargs): lg.info('-- INIT BOKEH DATATABLE') self.env.bk_table = self self.s = 0 # current sample self.ns = 0 # number of samples self.table_df = None self.update_params() self.flag_formatter = None self.value_formatter = None self.param_formatter = None self.env.dt_manual_update = True self._init_templates() self._init_datatable() self._init_sample_buttons() def _init_templates(self): switch_case = '' for c, v in CIRCLE_COLORS.items(): switch_case += "case '{}': return('{}'); break;\n".format( str(c), v) flag_template = """ <div style="font-weight: bold; color: <%= (function colorfromint() {{ switch (value) {{ {} default: return('black'); }} }}()) %>; " ><%= value %> </div> """.format(switch_case) self.flag_formatter = HTMLTemplateFormatter( template=flag_template ) # TODO: to show float numbers NumberFormatter should be used value_template = """ <div style="font-weight: <%= (function colorfromint() { switch (value) { case 'NaN': return('bold'); break default: return('normal'); } }()) %>; color: <%= (function colorfromint() { switch (value) { case 'NaN': return('red'); break default: return('black'); } }()) %>; " ><%= value %> </div> """ # lg.info('>> TEMPLATE: {}'.format(value_template)) self.value_formatter = HTMLTemplateFormatter(template=value_template) param_template = """ <div style="font-weight: bold;"><%= value %></div> """ # lg.info('>> TEMPLATE: {}'.format(param_template)) self.param_formatter = HTMLTemplateFormatter(template=param_template) def _init_datatable(self): columns = [ TableColumn( width=80, field="parameter", title="Parameter", formatter=self.param_formatter, editor=CellEditor() # non editable ), TableColumn(width=50, field="value", title="Value", formatter=self.value_formatter, editor=CellEditor()), TableColumn(width=40, field="flag", title="Flag", formatter=self.flag_formatter, editor=StringEditor()), ] self.table_df = pd.DataFrame( dict( parameter=self.params, value=[''] * len(self.params), flag=[''] * len(self.params), )) table_cds = ColumnDataSource(self.table_df) self.data_table = DataTable( width=190, height=125, source=table_cds, columns=columns, editable= True, # TODO: check if there is a better way than https://stackoverflow.com/a/49424647/4891717 fit_columns=False, # avoids horizontal scrolls bar index_position=None, # hides index column selectable=True, # this is needed to edit cells reorderable= False, # NOTE: this needs jquery ui, but it is not needed scroll_to_selection=False, # not needed sortable=False, # not needed ) self.data_table.source.on_change('data', self.on_change_data_source) def _init_sample_buttons(self): lg.info('-- SET SAMPLE BUTTONS') def next_sample(): lg.info('>> NEXT SAMPLE') if self.s < self.ns: self.env.doc.hold('collect') self.s += 1 self.sample_div.text = ' {} / {}'.format(self.s, self.ns) self.env.cur_nearby_prof = None # to reset the extra stt profile to plot self.env.dt_next_sample = True self._update_dt_sample() self.env.doc.unhold() def previous_sample(): lg.info('>> PREVIOUS SAMPLE') if self.s > 1: self.env.doc.hold('collect') self.s -= 1 self.sample_div.text = ' {} / {}'.format(self.s, self.ns) self.env.cur_nearby_prof = None self.env.dt_previous_sample = True self._update_dt_sample() self.env.doc.unhold() self.next_bt = Button(label=">", button_type="success", width=30) self.sample_div = Div( text='0 / 0', width=100, height=30, css_classes=['sample_div'], ) self.previous_bt = Button(label="<", button_type="success", width=30) self.next_bt.on_click(next_sample) self.previous_bt.on_click(previous_sample) def update_params(self): self.params = self.env.cruise_data.get_cols_by_type(['param']) self.params.insert(0, STNNBR) def update_dt_source(self): ''' Updates the datatable source depending on the selected samples ''' lg.info('-- UPDATE DATATABLE SOURCE') self.update_params() df = self.env.cds_df.iloc[self.env.selection] df_filter = self.env.cruise_data.get_cols_by_type( ['param', 'param_flag', 'qc_param_flag']) df_filter.insert(0, STNNBR) df = df.filter(df_filter) self.table_df = df # TODO: check if it has the same order as the selection) self.ns = len(self.env.selection) self.s = 1 if self.env.selection != [] else 0 self.sample_div.text = ' {} / {}'.format(self.s, self.ns) self._update_dt_sample() def _update_dt_sample(self): ''' Updates the datatable with the data of the self.s ''' lg.info('-- UPDATE DT SAMPLE') flag_values = [] param_values = [] if self.s != 0: self.env.sample_to_select = self.env.selection[self.s - 1] for p in self.params: v = self.table_df.loc[self.env.sample_to_select, p] # lg.info('>> V: {} | TYPE: {}'.format(v, type(v))) if not isinstance(v, str): if np.isnan(v): v = 'NaN' else: v = str(v) param_values.append(v) fp = p + FLAG_END if fp in self.table_df.columns: fv = self.table_df.loc[self.env.sample_to_select, fp] if not isinstance(fv, str): if np.isnan(fv): fv = 'NaN' else: fv = str(fv) flag_values.append(fv) else: flag_values.append('-') else: self.env.sample_to_select = None param_values = [''] * len(self.params) flag_values = [''] * len(self.params) new_vals_dict = { 'parameter': self.params, 'value': param_values, 'flag': flag_values } lg.info('>> NEW VALS DICT: \n\n{}'.format(new_vals_dict)) lg.info('>> LENS (PARAMETER, VALUE, FLAG) = ({}, {}, {})'.format( len(self.params), len(param_values), len(flag_values))) lg.info('>> DT_MANUAL_UPDATE IN _UPDATE_DT_SAMPLE: {}'.format( self.env.dt_manual_update)) # self.env.dt_manual_update = False # just in case self.data_table.source.data = new_vals_dict # self.env.dt_manual_update = True self.env.bk_sources.update_prof_sources(force_selection=True) def on_change_data_source(self, attr, old, new): indices = list(range(self.table_df.size)) # changes = [(param_name, index, old_value, new_value)] changes = [(self.params[i], i, j, k) for i, j, k in zip(indices, old['flag'], new['flag']) if j != k] # lg.info('>> CHANGES: {}'.format(changes)) # lg.info('>> SELECTION = {} | DF_MANUAL_UPDATE = {}'.format(self.env.selection, self.env.dt_manual_update)) # lg.info('>> DT_NEXT_SAMPLE = {} | DF_PREVIOUS_SAMPLE = {}'.format(self.env.dt_next_sample, self.env.dt_previous_sample)) error = False # This happens when the DataFrame is empty (Reset button or the initial state) if self.env.selection == [] and self.env.dt_manual_update: error = True if self.env.selection != [] and self.env.dt_manual_update and changes != []: # if len(changes) == 1: # NOTE: if it is a manual update the length should be 1 # but I am getting size 1 when the whole DataTable CDS is updated # I do not why is this happening if not (self.env.dt_next_sample or self.env.dt_previous_sample): for t in changes: if t[2] == '-': error = True self.env.bk_bridge.error_js( 'This parameter does not have any flag column associated.' ) else: try: new_flag_val = int(t[3]) except: error = True self.env.bk_bridge.error_js( 'The value should be an integer.') else: if new_flag_val not in self.env.bk_flags.all_flags_list: error = True self.env.bk_bridge.error_js( 'The cell value should be a valid flag value.' ) else: self.env.dt_manual_update = False self.env.bk_flags.update_flag_value( flag_to_update=t[0] + FLAG_END, flag_value=new_flag_val, row_indexes=[self.env.sample_to_select]) self.env.dt_manual_update = True else: self.env.dt_next_sample = False self.env.dt_previous_sample = False if error: self.rollback(changes) def rollback(self, changes): lg.info('-- DATATABLE ROLLBACK') if self.env.selection != []: for t in changes: patch = { 'flag': [ (t[1], t[2]), ] } # rollback to the old value self.env.dt_manual_update = False self.data_table.source.patch(patch) self.env.dt_manual_update = True else: if changes != []: for t in changes: patch = { 'flag': [ (t[1], ''), ] } # rollback to the initial value self.env.dt_manual_update = False self.data_table.source.patch(patch) self.env.dt_manual_update = True else: self.env.dt_manual_update = False self.table_df = pd.DataFrame( dict( parameter=self.params, # remove flags from here value=[''] * len(self.params), flag=[''] * len(self.params), )) self.data_table.source.data = self.table_df.to_dict('list') self.env.dt_manual_update = True
class PSUGUI(TabbedGUI): """@brief Responsible for plotting data on tab 0 with no other tabs.""" CFG_FILENAME = ".RS310P_GUI.cfg" AMPS = "amps" VOLTS = "volts" PLOT_SECONDS = "plotSeconds" CFG_DICT = {VOLTS: "5", AMPS: "1", PLOT_SECONDS: 300} def __init__(self, docTitle, bokehPort=12000): """@Constructor""" super().__init__(docTitle, bokehPort=bokehPort) self._figTable = [[]] self._grid = None self._textBuffer = "" self._psu = None self._on = False def info(self, msg): """@brief Display an info level message.""" self.statusBarWrapper.setStatus("INFO: " + msg) def error(self, msg): """@brief Display an error level message.""" self.statusBarWrapper.setStatus("ERROR: " + msg) def debug(self, msg): """@brief Display an error level message.""" pass def addRow(self): """@brief Add an empty row to the figures.""" self._figTable.append([]) def addToRow(self, fig): """@brief Add a figure to the end of the current row of figues. @param fig The figure to add.""" self._figTable[-1].append(fig) def createPlot( self, doc, ): """@brief create a plot figure. @param doc The document to add the plot to.""" self._doc = doc self._doc.title = "RS310P PSU Controller" self.statusBarWrapper = StatusBarWrapper() self._pconfig = ConfigManager(self, PSUGUI.CFG_FILENAME, PSUGUI.CFG_DICT) self._pconfig.load() plotPanel = self._getPlotPanel() self._tabList.append( Panel(child=plotPanel, title="DC Power Supply Control")) self._doc.add_root(Tabs(tabs=self._tabList)) self._doc.add_periodic_callback(self._viewUpdate, 500) def _viewUpdate(self): if self._on: volts, amps, watts = self._psu.getOutputStats() self._opTableWrapper.setRows([[volts, amps, watts]]) self._updatePlot(volts, amps, watts) def _updatePlot(self, volts, amps, watts): """@brief called periodically to update the plot trace.""" plotPoints = self.plotHistorySpinner.value * 2 now = datetime.now() newVolts = {'x': [now], 'y': [volts]} self._voltsSource.stream(newVolts, rollover=plotPoints) newAmps = {'x': [now], 'y': [amps]} self._ampsSource.stream(newAmps, rollover=plotPoints) newWatts = {'x': [now], 'y': [watts]} self._wattsSource.stream(newWatts, rollover=plotPoints) def _getPlotPanel(self): """@brief Add tab that shows plot data updates.""" self._figTable.append([]) self._voltsSource = ColumnDataSource({'x': [], 'y': []}) self._ampsSource = ColumnDataSource({'x': [], 'y': []}) self._wattsSource = ColumnDataSource({'x': [], 'y': []}) fig = figure(toolbar_location='above', x_axis_type="datetime", x_axis_location="below") fig.line(source=self._voltsSource, line_color="blue", legend_label="Volts") fig.line(source=self._ampsSource, line_color="green", legend_label="Amps") fig.line(source=self._wattsSource, line_color="red", legend_label="Watts") fig.legend.location = 'top_left' self._figTable[-1].append(fig) self._grid = gridplot(children=self._figTable, sizing_mode='scale_both', toolbar_location='right') self.selectSerialPort = Select(title="Serial Port:") self.selectSerialPort.options = glob.glob('/dev/ttyU*') self.outputVoltageSpinner = Spinner(title="Output Voltage (Volts)", low=0, high=40, step=0.5, value=self._pconfig.getAttr( PSUGUI.VOLTS)) self.currentLimitSpinner = Spinner(title="Currnet Limit (Amps)", low=0, high=10, step=0.25, value=self._pconfig.getAttr( PSUGUI.AMPS)) self.plotHistorySpinner = Spinner(title="Plot History (Seconds)", low=1, high=10000, step=1, value=self._pconfig.getAttr( PSUGUI.PLOT_SECONDS)) self._setButton = Button(label="Set") self._setButton.on_click(self._setHandler) self._setButton.disabled = True self._onButton = Button(label="On") self._onButton.on_click(self._psuOnHandler) shutdownButtonWrapper = ShutdownButtonWrapper(self._quit) controlPanel = column([ self.selectSerialPort, self._onButton, self.outputVoltageSpinner, self.currentLimitSpinner, self._setButton, self.plotHistorySpinner, shutdownButtonWrapper.getWidget() ]) self._opTableWrapper = ReadOnlyTableWrapper(("volts", "amps", "watts"), heightPolicy="fixed", height=65, showLastRows=0) plotPanel = column([self._grid, self._opTableWrapper.getWidget()]) panel2 = row([controlPanel, plotPanel]) plotPanel = column([panel2, self.statusBarWrapper.getWidget()]) return plotPanel def _quit(self): if self._on: self._psuOff() self._run(self._delayedShutdown) self._doc.clear() def _delayedShutdown(self): """@brief Allow time for browser page to clear before shutdown.""" volts = self.outputVoltageSpinner.value amps = self.currentLimitSpinner.value self._pconfig.addAttr(PSUGUI.VOLTS, volts) self._pconfig.addAttr(PSUGUI.AMPS, amps) self._pconfig.addAttr(PSUGUI.PLOT_SECONDS, self.plotHistorySpinner.value) self._pconfig.store() sleep(0.5) self._sendUpdateEvent( PSUGUIUpdateEvent(PSUGUIUpdateEvent.SHUTDOWN_SERVER)) def _psuOnHandler(self): """@brief event handler.""" #Stop the user from clicking the button again until this click has been processed. self._onButton.disabled = True #Turn the PSUon/off method outside GUI thread self._run(self._powerOnOff) def _setHandler(self): """@brief event handler.""" #Turn the PSUon/off method outside GUI thread self._run(self._setPSU) def _rxUpdateEvent(self, updateEvent): """@brief Receive an event into the GUI context to update the GUI. @param updateEvent An PSUGUIUpdateEvent instance.""" if updateEvent.id == PSUGUIUpdateEvent.UPDATE_STATUS_TEXT: self.statusBarWrapper.setStatus(updateEvent.argList[0]) elif updateEvent.id == PSUGUIUpdateEvent.CONNECTING_TO_PSU: self._onButton.button_type = "success" self._onButton.disabled = True self.statusBarWrapper.setStatus(updateEvent.argList[0]) elif updateEvent.id == PSUGUIUpdateEvent.PSU_CONNECT_FAILED: self._onButton.button_type = "default" self._onButton.disabled = False self._setButton.disabled = True self._setButton.button_type = "default" self.statusBarWrapper.setStatus(updateEvent.argList[0]) elif updateEvent.id == PSUGUIUpdateEvent.CONNECTED_TO_PSU: self._setButton.button_type = "success" self._setButton.disabled = False self._onButton.button_type = "success" self._onButton.disabled = False self._onButton.label = "Off" self.statusBarWrapper.setStatus("PSU ON") self._pconfig.addAttr(PSUGUI.VOLTS, self.outputVoltageSpinner.value) self._pconfig.addAttr(PSUGUI.AMPS, self.currentLimitSpinner.value) self._pconfig.addAttr(PSUGUI.PLOT_SECONDS, self.plotHistorySpinner.value) self._pconfig.store() elif updateEvent.id == PSUGUIUpdateEvent.TURNING_PSU_OFF: self._onButton.button_type = "default" self._setButton.disabled = True self._onButton.disabled = True self._setButton.button_type = "default" self.statusBarWrapper.setStatus("Turning PSU OFF") elif updateEvent.id == PSUGUIUpdateEvent.PSU_OFF: self._onButton.button_type = "default" self._onButton.disabled = False self._onButton.label = "On" self.statusBarWrapper.setStatus("PSU OFF") elif updateEvent.id == PSUGUIUpdateEvent.SET_PSU_STATE: volts = self.outputVoltageSpinner.value amps = self.currentLimitSpinner.value self._pconfig.addAttr(PSUGUI.VOLTS, volts) self._pconfig.addAttr(PSUGUI.AMPS, amps) self._pconfig.addAttr(PSUGUI.PLOT_SECONDS, self.plotHistorySpinner.value) self._pconfig.store() self.statusBarWrapper.setStatus( "Set {:.2f} volts with a {:.3f} amp current limit".format( volts, amps)) elif updateEvent.id == PSUGUIUpdateEvent.SHUTDOWN_SERVER: self.stopServer() print("PJA: SERVER SHUTDOWN.") def _powerOnOff(self): """@brief Called to turn the PSU on/off""" if self._on: self._psuOff() else: self._psuOn() def _setPSU(self): """@brief Called when the PSU is on to set the state of the PSU.""" volts = self.outputVoltageSpinner.value amps = self.currentLimitSpinner.value self._psu.setVoltage(volts) self._psu.setCurrentLimit(amps) self._sendUpdateEvent(UpdateEvent(PSUGUIUpdateEvent.SET_PSU_STATE)) def getSelectedSerialPort(self): """@brief Get the selected serial port. @return the selected Serial port or None if not selected.""" selectedSerialPort = None if len(self.selectSerialPort.options) == 1: selectedSerialPort = self.selectSerialPort.options[0] elif self.selectSerialPort.value: selectedSerialPort = self.selectSerialPort.value if not selectedSerialPort and len(self.selectSerialPort.options) > 0: selectedSerialPort = self.selectSerialPort.options[0] return selectedSerialPort def _psuOff(self): """@brief Turn the PSU off.""" self._sendUpdateEvent(UpdateEvent(PSUGUIUpdateEvent.TURNING_PSU_OFF)) self._on = False self._psu.setOutput(False) self._psu.disconnect() self._psu = None self._sendUpdateEvent(UpdateEvent(PSUGUIUpdateEvent.PSU_OFF)) def _psuOn(self): """@brief Connect to the PDU. @return True if successfully connected to a PSU.""" serialPort = None try: serialPort = self.getSelectedSerialPort() if serialPort: self._sendUpdateEvent( UpdateEvent(PSUGUIUpdateEvent.CONNECTING_TO_PSU, ("Connecting to {}".format(serialPort), ))) self._psu = ETMXXXXP(serialPort) self._psu.connect() self._psu.getOutput() #Ensure the voltage comes up from a low voltage rather than down #from a previously higher voltage self._psu.setVoltage(0) self._psu.setVoltage(self.outputVoltageSpinner.value) self._psu.setCurrentLimit(self.currentLimitSpinner.value) self._psu.setOutput(True) self._on = True self._sendUpdateEvent( UpdateEvent(PSUGUIUpdateEvent.CONNECTED_TO_PSU)) else: self._sendUpdateEvent( UpdateEvent(PSUGUIUpdateEvent.PSU_CONNECT_FAILED, ( "Failed to to connect to PSU as no serial port was selected.", ))) except Exception as ex: print(ex) if self._psu: self._psu.disconnect() self._psu = None if serialPort: self._sendUpdateEvent( UpdateEvent( PSUGUIUpdateEvent.PSU_CONNECT_FAILED, ("Failed to connect to PSU on {}".format(serialPort), ))) else: self._sendUpdateEvent( UpdateEvent( PSUGUIUpdateEvent.PSU_CONNECT_FAILED, ("Failed to connect to PSU on {}".format(serialPort), ))) self.setStatus("Failed to connect to PSU.")
def interactive_hist(adata, keys=['n_counts', 'n_genes'], bins='auto', max_bins=100, groups=None, fill_alpha=0.4, palette=None, display_all=True, tools='pan, reset, wheel_zoom, save', legend_loc='top_right', plot_width=None, plot_height=None, save=None, *args, **kwargs): """Utility function to plot distributions with variable number of bins. Params -------- adata: AnnData object annotated data object keys: list(str), optional (default: `['n_counts', 'n_genes']`) keys in `adata.obs` or `adata.var` where the distibutions are stored bins: int; str, optional (default: `auto`) number of bins used for plotting or str from numpy.histogram max_bins: int, optional (default: `1000`) maximum number of bins possible groups: list(str), (default: `None`) keys in `adata.obs.obs_keys()`, groups by all possible combinations of values, e.g. for 3 plates and 2 time points, we would create total of 6 groups fill_alpha: float[0.0, 1.0], (default: `0.4`) alpha channel of the fill color palette: list(str), optional (default: `None`) palette to use display_all: bool, optional (default: `True`) display the statistics for all data tools: str, optional (default: `'pan,reset, wheel_zoom, save'`) palette of interactive tools for the user legend_loc: str, (default: `'top_right'`) position of the legend legend_loc: str, default(`'top_left'`) position of the legend plot_width: int, optional (default: `None`) width of the plot plot_height: int, optional (default: `None`) height of the plot save: Union[os.PathLike, Str, NoneType], optional (default: `None`) path where to save the plot *args, **kwargs: arguments, keyword arguments addition argument to bokeh.models.figure Returns -------- None """ if max_bins < 1: raise ValueError(f'`max_bins` must >= 1') palette = Set1[9] + Set2[8] + Set3[12] if palette is None else palette # check the input for key in keys: if key not in adata.obs.keys() and \ key not in adata.var.keys() and \ key not in adata.var_names: raise ValueError(f'The key `{key}` does not exist in `adata.obs`, `adata.var` or `adata.var_names`.') def _create_adata_groups(): if groups is None: return [adata], [('all',)] combs = list(product(*[set(adata.obs[g]) for g in groups])) adatas= [adata[reduce(lambda l, r: l & r, (adata.obs[k] == v for k, v in zip(groups, vals)), True)] for vals in combs] + [adata] if display_all: combs += [('all',)] adatas += [adata] return adatas, combs # group_v_combs contains the value combinations ad_gs = _create_adata_groups() cols = [] for key in keys: callbacks = [] fig = figure(*args, tools=tools, **kwargs) slider = Slider(start=1, end=max_bins, value=0, step=1, title='Bins') plots = [] for j, (ad, group_vs) in enumerate(filter(lambda ad_g: ad_g[0].n_obs > 0, zip(*ad_gs))): if key in ad.obs.keys(): orig = ad.obs[key] hist, edges = np.histogram(orig, density=True, bins=bins) elif key in ad.var.keys(): orig = ad.var[key] hist, edges = np.histogram(orig, density=True, bins=bins) else: orig = ad[:, key].X hist, edges = np.histogram(orig, density=True, bins=bins) slider.value = len(hist) # case when automatic bins max_bins = max(max_bins, slider.value) # original data, used for recalculation of histogram in JS code orig = ColumnDataSource(data=dict(values=orig)) # data that we update in JS code source = ColumnDataSource(data=dict(hist=hist, l_edges=edges[:-1], r_edges=edges[1:])) legend = ', '.join(': '.join(map(str, gv)) for gv in zip(groups, group_vs)) \ if groups is not None else 'all' p = fig.quad(source=source, top='hist', bottom=0, left='l_edges', right='r_edges', fill_color=palette[j], legend_label=legend if legend_loc is not None else None, muted_alpha=0, line_color="#555555", fill_alpha=fill_alpha) # create callback and slider callback = CustomJS(args=dict(source=source, orig=orig), code=_inter_hist_js_code) callback.args['bins'] = slider callbacks.append(callback) # add the current plot so that we can set it # visible/invisible in JS code plots.append(p) slider.end = max_bins # slider now updates all values slider.js_on_change('value', *callbacks) button = Button(label='Toggle', button_type='primary') button.callback = CustomJS( args={'plots': plots}, code=''' for (var i = 0; i < plots.length; i++) { plots[i].muted = !plots[i].muted; } ''' ) if legend_loc is not None: fig.legend.location = legend_loc fig.legend.click_policy = 'mute' fig.xaxis.axis_label = key fig.yaxis.axis_label = 'normalized frequency' _set_plot_wh(fig, plot_width, plot_height) cols.append(column(slider, button, fig)) if _bokeh_version > (1, 0, 4): from bokeh.layouts import grid plot = grid(children=cols, ncols=2) else: cols = list(map(list, np.array_split(cols, np.ceil(len(cols) / 2)))) plot = layout(children=cols, sizing_mode='fixed', ncols=2) if save is not None: save = save if str(save).endswith('.html') else str(save) + '.html' bokeh_save(plot, save) else: show(plot)
def init_widgets(): global widgets widgets = WrapBokeh(PAGE_URL, app.logger) widgets.add( "s_age", Slider(title='Age', value=25, start=1, end=99, step=1, callback_policy='mouseup', width=200, css_classes=['s_age'])) widgets.add( "dp_birthday", DatePicker(title="Birthday", min_date=None, max_date=datetime.today(), value=datetime.today(), width=300, css_classes=['dp_birthday'])) widgets.add( "msel_fruit", MultiSelect(options=[('Apples', 'Apples'), ('Strawberries', 'Strawberries'), ('Oranges', 'Oranges'), ('Grapefruit', 'Grapefruit'), ('Banannas', 'Banannas')], value=[], title="Fruit", css_classes=['msel_fruit'])) widgets.add( "ds_birthday", DateSlider(title="Birthday", end=datetime.today(), start=datetime.today() - timedelta(days=30), step=1, value=datetime.today(), callback_policy='mouseup', css_classes=['ds_birthday'])) widgets.add( "s_amp", Slider(title='Amplitude', value=1, start=0, end=2, step=0.1, callback_policy='mouseup', width=200, css_classes=['s_amp'])) widgets.add("b_test", Button(label="Press me!", css_classes=['b_test'])) widgets.add("toggle_1", Toggle(label="Toggle me!", css_classes=['toggle_1'])) widgets.add( "dropdn_1", Dropdown(label="Menu", menu=[("First", '0'), ("Second", '1'), None, ("End", '2')], css_classes=['dropdn_1'])) widgets.add( "sel_nexturl", Select(options=[('99', 'Select Next Page'), ('1', 'Ajax Stream Example'), ('2', 'Form Example'), ('3', 'Page C'), ('4', 'Page D')], value=None, title="Select URL", css_classes=['sel_nexturl'])) widgets.add( "cbbg_music", CheckboxButtonGroup(labels=["Rock0", "Country0", "Classical0"], active=[], css_classes=['cbbg_music'])) widgets.add( "cbg_music", CheckboxGroup(labels=["Rock1", "Country1", "Classical1"], active=[], css_classes=['cbg_music'])) widgets.add( "rbg_music", RadioButtonGroup(labels=["Rock2", "Country2", "Classical2"], active=None, css_classes=['rbg_music'])) widgets.add( "rg_music", RadioGroup(labels=["Rock3", "Country3", "Classical3"], active=None, css_classes=['rg_music'])) widgets.add( "rslider_amp", RangeSlider(title='Amplitude', value=(0.5, 1.5), start=0, end=2, step=0.1, callback_policy='mouseup', width=200, css_classes=['rslider_amp'])) widgets.init()
OPTIONS = [str(i) for i in range(20)] 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()) """)) # # SELECT menu GUI # selectoptions = ["Postive tested on Covid-19 virus", "Negative tested on Covid-19 virus", "Show both"] # resultSelect = Select(title="What to show", options=selectoptions) # TOOL BUTTON CALLBACKS ----------------------------------------------------------------------------------------------- # Test button GUI lab = "Click me!" but = Button(label=lab) def callback_button1( ): # simple test callback -> changes lable on button and reports a click to the console print("button was clicked!") but.on_click(callback_button1) # links the clickedcode to the button # [END] TOOL BUTTON CALLBACKS ----------------------------------------------------------------------------------------- # general webpage & plots title = Div( text= "<b>Visualisation tool of patients tested for Covid-19 of the Hospital Israelita Albert Einstein, at São Paulo, Brazil</b>",
def highlight_indices(adata, key, basis='diffmap', components=[1, 2], cell_keys='', legend_loc='top_right', plot_width=None, plot_height=None, tools='pan, reset, wheel_zoom, save'): """ Plot cell indices. Useful when trying to set adata.uns['iroot']. Params -------- adata: AnnData Object annotated data object key: str key in `adata.obs_keys()` to color basis: str, optional (default: `'diffmap'`) basis to use cell_keys: str, list(str), optional (default: `''`) keys to display from `adata.obs_keys()` when hovering over cell components: list[int], optional (default: `[1, 2]`) which components of the basis to use legend_loc: str, optional (default `'top_right'`) location of the legend tools: str, optional (default: `'pan, reset, wheel_zoom, save'`) tools for the plot plot_width: int, optional (default: `None`) width of the plot plot_width: int, optional (default: `None`) height of the plot Returns -------- None """ if key not in adata.obs: raise ValueError(f'{key} not found in `adata.obs`') if f'X_{basis}' not in adata.obsm_keys(): raise ValueError(f'basis `X_{basis}` not found in `adata.obsm`') if not isinstance(components, type(np.array)): components = np.array(components) if isinstance(cell_keys, str): cell_keys = list(dict.fromkeys(map(str.strip, cell_keys.split(',')))) if cell_keys != ['']: assert all(map(lambda k: k in adata.obs.keys(), cell_keys)), 'Not all keys are in `adata.obs.keys()`.' else: cell_keys = [] df = pd.DataFrame(adata.obsm[f'X_{basis}'][:, components - (basis != 'diffmap')], columns=['x', 'y']) for k in cell_keys: df[k] = list(map(str, adata.obs[k])) df['index'] = range(len(df)) df[key] = list(adata.obs[key]) if hasattr(adata, 'obs_names'): cell_keys.insert(0, 'name') df['name'] = list(adata.obs_names) if 'index' not in cell_keys: cell_keys.insert(0, 'index') palette = adata.uns.get(f'{key}_colors', viridis(len(df[key].unique()))) p = figure(title=f'{key}', tools=tools) _set_plot_wh(p, plot_width, plot_height) key_col = adata.obs[key].astype('category') if adata.obs[key].dtype.name != 'category' else adata.obs[key] renderers = [] for c, color in zip(key_col.cat.categories, palette): data = ColumnDataSource(df[df[key] == c]) renderers.append([p.scatter(x='x', y='y', size=10, color=color, source=data, muted_alpha=0)]) hover_cell = HoverTool(renderers=list(np.ravel(renderers)), tooltips=[(f'{k}', f'@{k}') for k in cell_keys]) if legend_loc is not None: legend = Legend(items=list(zip(map(str, key_col.cat.categories), renderers)), location=legend_loc, click_policy='mute') p.add_layout(legend) p.legend.location = legend_loc p.xaxis.axis_label = f'{basis}_{components[0]}' p.yaxis.axis_label = f'{basis}_{components[1]}' source = ColumnDataSource(df) labels = LabelSet(x='x', y='y', text='index', x_offset=4, y_offset=4, level='glyph', source=source, render_mode='canvas') labels.visible = False p.add_tools(hover_cell) p.add_layout(labels) button = Button(label='Toggle Indices', button_type='primary') button.callback = CustomJS(args=dict(l=labels), code='l.visible = !l.visible;') show(column(button, p))