def spectroscopy_plot(source_id): """TODO normalization? should this be handled at data ingestion or plot-time?""" source = Source.query.get(source_id) spectra = Source.query.get(source_id).spectra if len(spectra) == 0: return None, None, None color_map = dict(zip([s.id for s in spectra], viridis(len(spectra)))) data = pd.concat( [pd.DataFrame({'wavelength': s.wavelengths, 'flux': s.fluxes, 'id': s.id, 'instrument': s.instrument.telescope.nickname}) for i, s in enumerate(spectra)] ) split = data.groupby('id') hover = HoverTool(tooltips=[('wavelength', '$x'), ('flux', '$y'), ('instrument', '@instrument')]) plot = figure(plot_width=600, plot_height=300, sizing_mode='scale_both', tools='box_zoom,wheel_zoom,pan,reset', active_drag='box_zoom') plot.add_tools(hover) model_dict = {} for i, (key, df) in enumerate(split): model_dict['s' + str(i)] = plot.line(x='wavelength', y='flux', color=color_map[key], source=ColumnDataSource(df)) plot.xaxis.axis_label = 'Wavelength (Å)' plot.yaxis.axis_label = 'Flux' plot.toolbar.logo = None # TODO how to choose a good default? plot.y_range = Range1d(0, 1.03 * data.flux.max()) toggle = CheckboxWithLegendGroup(labels=[s.instrument.telescope.nickname for s in spectra], active=list(range(len(spectra))), width=100, colors=[color_map[k] for k, df in split]) toggle.callback = CustomJS(args={'toggle': toggle, **model_dict}, code=""" for (let i = 0; i < toggle.labels.length; i++) { eval("s" + i).visible = (toggle.active.includes(i)) } """) elements = CheckboxWithLegendGroup( labels=list(SPEC_LINES.keys()), active=[], width=80, colors=[c for w, c in SPEC_LINES.values()] ) z = TextInput(value=str(source.redshift), title="z:") v_exp = TextInput(value='0', title="v_exp:") for i, (wavelengths, color) in enumerate(SPEC_LINES.values()): el_data = pd.DataFrame({'wavelength': wavelengths}) el_data['x'] = el_data['wavelength'] * (1 + source.redshift) model_dict[f'el{i}'] = plot.segment(x0='x', x1='x', # TODO change limits y0=0, y1=1e-13, color=color, source=ColumnDataSource(el_data)) model_dict[f'el{i}'].visible = False # TODO callback policy: don't require submit for text changes? elements.callback = CustomJS(args={'elements': elements, 'z': z, 'v_exp': v_exp, **model_dict}, code=""" let c = 299792.458; // speed of light in km / s for (let i = 0; i < elements.labels.length; i++) { let el = eval("el" + i); el.visible = (elements.active.includes(i)) el.data_source.data.x = el.data_source.data.wavelength.map( x_i => (x_i * (1 + parseFloat(z.value)) / (1 + parseFloat(v_exp.value) / c)) ); el.data_source.change.emit(); } """) z.callback = elements.callback v_exp.callback = elements.callback layout = row(plot, toggle, elements, column(z, v_exp)) return _plot_to_json(layout)
in_model_rdn_btn.callback = CustomJS( args=dict(radioButton=in_model_rdn_btn), code=bgui_js_handlers.in_model_rdn_btn_callback_code) in_model_rdn_btn.on_change("active", selected_model_changed) in_min_time = ColumnDataSource(data=dict(val=[MIN_LP_TIME])) in_time_slider = Slider(start=MIN_LP_TIME, end=MAX_LP_TIME, value=10, step=1, title="Set the time for calculation [hrs]") in_capacity_txt = TextInput(value="100", title="Set the capacity:") in_capacity_txt.callback = CustomJS( args={ 'min_time': in_min_time, 'time': in_time_slider, 'radioButton': in_model_rdn_btn }, code=bgui_js_handlers.in_capacity_txt_callback_code) calculation_button = Button(label='Calculate', button_type="success") upl_file_souce = ColumnDataSource({'file_contents': [], 'file_name': []}) upl_file_souce.on_change('data', upl_file_callback) upload_btn = Button(label="Upload", button_type="default") upload_btn.callback = CustomJS(args=dict(file_source=upl_file_souce), code=bgui_js_handlers.upload_btn_callback_code) ################### RESULTS - bokeh models ####################### BED_fig = figure(x_range=(0, 1151), y_range=(0, 2049), plot_width=750, plot_height=36 * NUM_PATIENTS) #toolbar_location="above"