def test_js_on_change_executes(self, bokeh_model_page): slider = Slider(start=0, end=10, value=1, title="bar", css_classes=["foo"], width=300) slider.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value"))) page = bokeh_model_page(slider) drag_slider(page.driver, ".foo", 150) results = page.results assert float(results['value']) > 1 assert page.has_no_console_errors()
def slider(): x = np.linspace(0, 10, 100) y = np.sin(x) source = ColumnDataSource(data=dict(x=x, y=y)) plot = figure( y_range=(-10, 10), tools='', toolbar_location=None, title="Sliders example") plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) amp_slider = Slider(start=0.1, end=10, value=1, step=.1, title="Amplitude") freq_slider = Slider(start=0.1, end=10, value=1, step=.1, title="Frequency") phase_slider = Slider(start=0, end=6.4, value=0, step=.1, title="Phase") offset_slider = Slider(start=-5, end=5, value=0, step=.1, title="Offset") callback = CustomJS(args=dict(source=source, amp=amp_slider, freq=freq_slider, phase=phase_slider, offset=offset_slider), code=""" const data = source.data; const A = amp.value; const k = freq.value; const phi = phase.value; const B = offset.value; const x = data['x'] const y = data['y'] for (var i = 0; i < x.length; i++) { y[i] = B + A*Math.sin(k*x[i]+phi); } source.change.emit(); """) amp_slider.js_on_change('value', callback) freq_slider.js_on_change('value', callback) phase_slider.js_on_change('value', callback) offset_slider.js_on_change('value', callback) widgets = column(amp_slider, freq_slider, phase_slider, offset_slider) return [widgets, plot]
def photometry_plot(obj_id, user, width=600, height=300): """Create scatter plot of photometry for object. Parameters ---------- obj_id : str ID of Obj to be plotted. Returns ------- (str, str) Returns (docs_json, render_items) json for the desired plot. """ data = pd.read_sql( DBSession().query( Photometry, Telescope.nickname.label("telescope"), Instrument.name.label("instrument"), ).join(Instrument, Instrument.id == Photometry.instrument_id).join( Telescope, Telescope.id == Instrument.telescope_id).filter( Photometry.obj_id == obj_id).filter( Photometry.groups.any( Group.id.in_([g.id for g in user.groups]))).statement, DBSession().bind) if data.empty: return None, None, None data['color'] = [get_color(f) for f in data['filter']] data['label'] = [ f'{i} {f}-band' for i, f in zip(data['instrument'], data['filter']) ] data['zp'] = PHOT_ZP data['magsys'] = 'ab' data['alpha'] = 1. data['lim_mag'] = -2.5 * np.log10( data['fluxerr'] * DETECT_THRESH) + data['zp'] # Passing a dictionary to a bokeh datasource causes the frontend to die, # deleting the dictionary column fixes that del data['original_user_data'] # keep track of things that are only upper limits data['hasflux'] = ~data['flux'].isna() # calculate the magnitudes - a photometry point is considered "significant" # or "detected" (and thus can be represented by a magnitude) if its snr # is above DETECT_THRESH obsind = data['hasflux'] & (data['flux'].fillna(0.) / data['fluxerr'] >= DETECT_THRESH) data.loc[~obsind, 'mag'] = None data.loc[obsind, 'mag'] = -2.5 * np.log10(data[obsind]['flux']) + PHOT_ZP # calculate the magnitude errors using standard error propagation formulae # https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulae data.loc[~obsind, 'magerr'] = None coeff = 2.5 / np.log(10) magerrs = np.abs(coeff * data[obsind]['fluxerr'] / data[obsind]['flux']) data.loc[obsind, 'magerr'] = magerrs data['obs'] = obsind data['stacked'] = False split = data.groupby('label', sort=False) # show middle 98% of data finite = np.isfinite(data['flux']) fdata = data[finite] lower = np.percentile(fdata['flux'], 1.) upper = np.percentile(fdata['flux'], 99.) lower -= np.abs(lower) * 0.1 upper += np.abs(upper) * 0.1 plot = figure(plot_width=width, plot_height=height, active_drag='box_zoom', tools='box_zoom,wheel_zoom,pan,reset,save', y_range=(lower, upper)) imhover = HoverTool(tooltips=tooltip_format) plot.add_tools(imhover) model_dict = {} for i, (label, sdf) in enumerate(split): # for the flux plot, we only show things that have a flux value df = sdf[sdf['hasflux']] key = f'obs{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker='circle', fill_color='color', alpha='alpha', source=ColumnDataSource(df), ) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker='circle', fill_color='color', source=ColumnDataSource(data=dict(mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], stacked=[], instrument=[]))) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df.iterrows(): px = ro['mjd'] py = ro['flux'] err = ro['fluxerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource(data=dict(xs=y_err_x, ys=y_err_y, color=df['color'], alpha=[1.] * len(df)))) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', source=ColumnDataSource(data=dict(xs=[], ys=[], color=[]))) plot.xaxis.axis_label = 'MJD' plot.yaxis.axis_label = 'Flux (μJy)' plot.toolbar.logo = None toggle = CheckboxWithLegendGroup(labels=list(data.label.unique()), active=list( range(len(data.label.unique()))), colors=list(data.color.unique())) # TODO replace `eval` with Namespaces # https://github.com/bokeh/bokeh/pull/6340 toggle.callback = CustomJS(args={ 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'togglef.js')).read()) slider = Slider(start=0., end=15., value=0., step=1., title='binsize (days)') callback = CustomJS( args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackf.js')).read().replace('default_zp', str(PHOT_ZP)).replace( 'detect_thresh', str(DETECT_THRESH))) slider.js_on_change('value', callback) layout = row(plot, toggle) layout = column(slider, layout) p1 = Panel(child=layout, title='Flux') # now make the mag light curve ymax = 1.1 * data['lim_mag'] ymin = 0.9 * data['lim_mag'] if len(data['obs']) > 0: ymax[data['obs']] = (data['mag'] + data['magerr']) * 1.1 ymin[data['obs']] = (data['mag'] - data['magerr']) * 0.9 plot = figure(plot_width=width, plot_height=height, active_drag='box_zoom', tools='box_zoom,wheel_zoom,pan,reset,save', y_range=(np.nanmax(ymax), np.nanmin(ymin)), toolbar_location='above') imhover = HoverTool(tooltips=tooltip_format) plot.add_tools(imhover) model_dict = {} for i, (label, df) in enumerate(split): key = f'obs{i}' model_dict[key] = plot.scatter(x='mjd', y='mag', color='color', marker='circle', fill_color='color', alpha='alpha', source=ColumnDataSource(df[df['obs']])) imhover.renderers.append(model_dict[key]) unobs_source = df[~df['obs']].copy() unobs_source.loc[:, 'alpha'] = 0.8 key = f'unobs{i}' model_dict[key] = plot.scatter(x='mjd', y='lim_mag', color='color', marker='inverted_triangle', fill_color='white', line_color='color', alpha='alpha', source=ColumnDataSource(unobs_source)) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='mag', color='color', marker='circle', fill_color='color', source=ColumnDataSource(data=dict(mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[]))) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df[df['obs']].iterrows(): px = ro['mjd'] py = ro['mag'] err = ro['magerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource(data=dict(xs=y_err_x, ys=y_err_y, color=df[df['obs']]['color'], alpha=[1.] * len(df[df['obs']])))) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', source=ColumnDataSource(data=dict(xs=[], ys=[], color=[]))) key = f'unobsbin{i}' model_dict[key] = plot.scatter( x='mjd', y='lim_mag', color='color', marker='inverted_triangle', fill_color='white', line_color='color', alpha=0.8, source=ColumnDataSource(data=dict(mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[]))) imhover.renderers.append(model_dict[key]) key = f'all{i}' model_dict[key] = ColumnDataSource(df) key = f'bold{i}' model_dict[key] = ColumnDataSource(df[[ 'mjd', 'flux', 'fluxerr', 'mag', 'magerr', 'filter', 'zp', 'magsys', 'lim_mag', 'stacked' ]]) plot.xaxis.axis_label = 'MJD' plot.yaxis.axis_label = 'AB mag' plot.toolbar.logo = None toggle = CheckboxWithLegendGroup(labels=list(data.label.unique()), active=list( range(len(data.label.unique()))), colors=list(data.color.unique())) # TODO replace `eval` with Namespaces # https://github.com/bokeh/bokeh/pull/6340 toggle.callback = CustomJS(args={ 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'togglem.js')).read()) slider = Slider(start=0., end=15., value=0., step=1., title='Binsize (days)') button = Button(label="Export Bold Light Curve to CSV") button.callback = CustomJS(args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', "download.js")).read().replace( 'objname', obj_id).replace( 'default_zp', str(PHOT_ZP))) toplay = row(slider, button) callback = CustomJS( args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackm.js')).read().replace('default_zp', str(PHOT_ZP)).replace( 'detect_thresh', str(DETECT_THRESH))) slider.js_on_change('value', callback) layout = row(plot, toggle) layout = column(toplay, layout) p2 = Panel(child=layout, title='Mag') tabs = Tabs(tabs=[p2, p1]) return _plot_to_json(tabs)
def geoplot( gdf_in, geometry_column="geometry", figure=None, figsize=None, title="", xlabel="Longitude", ylabel="Latitude", xlim=None, ylim=None, color="blue", colormap=None, colormap_uselog=False, colormap_range=None, category=None, dropdown=None, slider=None, slider_range=None, slider_name="", show_colorbar=True, colorbar_tick_format=None, xrange=None, yrange=None, hovertool=True, hovertool_columns=[], hovertool_string=None, simplify_shapes=None, tile_provider="CARTODBPOSITRON_RETINA", tile_provider_url=None, tile_attribution="", tile_alpha=1, panning=True, zooming=True, toolbar_location="right", show_figure=True, return_figure=True, return_html=False, legend=True, webgl=True, **kwargs, ): """Doc-String: TODO""" # Imports: import bokeh.plotting from bokeh.plotting import show from bokeh.models import ( HoverTool, LogColorMapper, LinearColorMapper, GeoJSONDataSource, WheelZoomTool, ColorBar, BasicTicker, LogTicker, Select, Slider, ColumnDataSource, ) from bokeh.models.callbacks import CustomJS from bokeh.models.widgets import Dropdown from bokeh.palettes import all_palettes from bokeh.layouts import row, column # Make a copy of the input geodataframe: gdf = gdf_in.copy() # Check layertypes: if type(gdf) != pd.DataFrame: layertypes = [] if "Point" in str(gdf.geom_type.unique()): layertypes.append("Point") if "Line" in str(gdf.geom_type.unique()): layertypes.append("Line") if "Polygon" in str(gdf.geom_type.unique()): layertypes.append("Polygon") if len(layertypes) > 1: raise Exception( f"Can only plot GeoDataFrames/Series with single type of geometry (either Point, Line or Polygon). Provided is a GeoDataFrame/Series with types: {layertypes}" ) else: layertypes = ["Point"] # Get and check provided parameters for geoplot: figure_options = { "title": title, "x_axis_label": xlabel, "y_axis_label": ylabel, "plot_width": 600, "plot_height": 400, "toolbar_location": toolbar_location, "active_scroll": "wheel_zoom", "x_axis_type": "mercator", "y_axis_type": "mercator", } if not figsize is None: width, height = figsize figure_options["plot_width"] = width figure_options["plot_height"] = height if webgl: figure_options["output_backend"] = "webgl" if type(gdf) != pd.DataFrame: # Convert GeoDataFrame to Web Mercator Projection: gdf.to_crs(epsg=3857, inplace=True) # Simplify shapes if wanted: if isinstance(simplify_shapes, numbers.Number): if layertypes[0] in ["Line", "Polygon"]: gdf[geometry_column] = gdf[geometry_column].simplify(simplify_shapes) elif not simplify_shapes is None: raise ValueError( "<simplify_shapes> parameter only accepts numbers or None." ) # Check for category, dropdown or slider (choropleth map column): category_options = 0 if not category is None: category_options += 1 category_columns = [category] if not dropdown is None: category_options += 1 category_columns = dropdown if not slider is None: category_options += 1 category_columns = slider if category_options > 1: raise ValueError( "Only one of <category>, <dropdown> or <slider> parameters is allowed to be used at once." ) # Check for category (single choropleth plot): if category is None: pass elif isinstance(category, (list, tuple)): raise ValueError( "For <category>, please provide an existing single column of the GeoDataFrame." ) elif category in gdf.columns: pass else: raise ValueError( f"Could not find column '{category}' in GeoDataFrame. For <category>, please provide an existing single column of the GeoDataFrame." ) # Check for dropdown (multiple choropleth plots via dropdown selection): if dropdown is None: pass elif not isinstance(dropdown, (list, tuple)): raise ValueError( "For <dropdown>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in dropdown: if col not in gdf.columns: raise ValueError( f"Could not find column '{col}' for <dropdown> in GeoDataFrame. " ) # Check for slider (multiple choropleth plots via slider selection): if slider is None: pass elif not isinstance(slider, (list, tuple)): raise ValueError( "For <slider>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in slider: if col not in gdf.columns: raise ValueError( f"Could not find column '{col}' for <slider> in GeoDataFrame. " ) if not slider_range is None: if not isinstance(slider_range, Iterable): raise ValueError( "<slider_range> has to be a type that is iterable like list, tuple, range, ..." ) else: slider_range = list(slider_range) if len(slider_range) != len(slider): raise ValueError( "The number of elements in <slider_range> has to be the same as in <slider>." ) steps = [] for i in range(len(slider_range) - 1): steps.append(slider_range[i + 1] - slider_range[i]) if len(set(steps)) > 1: raise ValueError( "<slider_range> has to have equal step size between each elements (like a range-object)." ) else: slider_step = steps[0] slider_start = slider_range[0] slider_end = slider_range[-1] # Check colormap if either <category>, <dropdown> or <slider> is choosen: if category_options == 1: if colormap is None: colormap = blue_colormap elif isinstance(colormap, (tuple, list)): if len(colormap) > 1: pass else: raise ValueError( f"<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): {list(all_palettes.keys())}" ) elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] colormap = colormap[max(colormap.keys())] else: raise ValueError( f"Could not find <colormap> with name {colormap}. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): {list(all_palettes.keys())}" ) else: raise ValueError( f"<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): {list(all_palettes.keys())}" ) else: if isinstance(color, str): colormap = [color] elif color is None: colormap = ["blue"] else: raise ValueError( "<color> has to be a string specifying the fill_color of the map glyph." ) # Check xlim & ylim: if xlim is not None: if isinstance(xlim, (tuple, list)): if len(xlim) == 2: xmin, xmax = xlim for _ in [xmin, xmax]: if not -180 < _ <= 180: raise ValueError( "Limits for x-axis (=Longitude) have to be between -180 and 180." ) if not xmin < xmax: raise ValueError("xmin has to be smaller than xmax.") from pyproj import Transformer transformer = Transformer.from_crs("epsg:4326", "epsg:3857") xmin = transformer.transform(0, xmin)[0] xmax = transformer.transform(0, xmax)[0] figure_options["x_range"] = (xmin, xmax) else: raise ValueError( "Limits for x-axis (=Longitude) have to be of form [xmin, xmax] with values between -180 and 180." ) else: raise ValueError( "Limits for x-axis (=Longitude) have to be of form [xmin, xmax] with values between -180 and 180." ) if ylim is not None: if isinstance(ylim, (tuple, list)): if len(ylim) == 2: ymin, ymax = ylim for _ in [ymin, ymax]: if not -90 < _ <= 90: raise ValueError( "Limits for y-axis (=Latitude) have to be between -90 and 90." ) if not ymin < ymax: raise ValueError("ymin has to be smaller than ymax.") from pyproj import Transformer transformer = Transformer.from_crs("epsg:4326", "epsg:3857") ymin = transformer.transform(ymin,0)[1] ymax = transformer.transform(ymax,0)[1] figure_options["y_range"] = (ymin, ymax) else: raise ValueError( "Limits for y-axis (=Latitude) have to be of form [ymin, ymax] with values between -90 and 90." ) else: raise ValueError( "Limits for y-axis (=Latitude) have to be of form [ymin, ymax] with values between -90 and 90." ) # Create Figure to draw: old_layout = None if figure is None: p = bokeh.plotting.figure(**figure_options) # Add Tile Source as Background: p = _add_backgroundtile( p, tile_provider, tile_provider_url, tile_attribution, tile_alpha ) elif isinstance(figure, type(bokeh.plotting.figure())): p = figure elif isinstance(figure, type(column())): old_layout = figure p = _get_figure(old_layout) else: raise ValueError( "Parameter <figure> has to be of type bokeh.plotting.figure or bokeh.layouts.column." ) # Get ridd of zoom on axes: for t in p.tools: if type(t) == WheelZoomTool: t.zoom_on_axis = False # Hide legend if wanted: legend_input = legend if isinstance(legend, str): pass else: legend = "GeoLayer" # Define colormapper: if len(colormap) == 1: kwargs["fill_color"] = colormap[0] elif not category is None: # Check if category column is numerical: if not issubclass(gdf[category].dtype.type, np.number): raise NotImplementedError( f"<category> plot only yet implemented for numerical columns. Column '{category}' is not numerical." ) field = category colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[field].min() colormapper_options["high"] = gdf[field].max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = str(field) elif not dropdown is None: # Check if all columns in dropdown selection are numerical: for col in dropdown: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( f"<dropdown> plot only yet implemented for numerical columns. Column '{col}' is not numerical." ) field = dropdown[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[dropdown].min().min() colormapper_options["high"] = gdf[dropdown].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} legend = " " + field elif not slider is None: # Check if all columns in dropdown selection are numerical: for col in slider: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( f"<slider> plot only yet implemented for numerical columns. Column '{col}' is not numerical." ) field = slider[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[slider].min().min() colormapper_options["high"] = gdf[slider].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = "Geolayer" # Check that only hovertool_columns or hovertool_string is used: if isinstance(hovertool_columns, (list, tuple, str)): if len(hovertool_columns) > 0 and hovertool_string is not None: raise ValueError( "Either <hovertool_columns> or <hovertool_string> can be used, but not both at the same time." ) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) if hovertool_string is not None: hovertool_columns = "all" # Check for Hovertool columns: if hovertool: if not isinstance(hovertool_columns, (list, tuple)): if hovertool_columns == "all": hovertool_columns = list( filter(lambda col: col != geometry_column, gdf.columns) ) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) elif len(hovertool_columns) == 0: if not category is None: hovertool_columns = [category] elif not dropdown is None: hovertool_columns = dropdown elif not slider is None: hovertool_columns = slider else: hovertool_columns = [] else: for col in hovertool_columns: if col not in gdf.columns: raise ValueError( f"Could not find columns '{col}' in GeoDataFrame. <hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) else: if category is None: hovertool_columns = [] else: hovertool_columns = [category] # Reduce DataFrame to needed columns: if type(gdf) == pd.DataFrame: gdf["Geometry"] = 0 additional_columns = ["x", "y"] else: additional_columns = [geometry_column] for kwarg, value in kwargs.items(): if isinstance(value, Hashable): if value in gdf.columns: additional_columns.append(value) if category_options == 0: gdf = gdf[list(set(hovertool_columns) | set(additional_columns))] else: gdf = gdf[ list( set(hovertool_columns) | set(category_columns) | set(additional_columns) ) ] gdf["Colormap"] = gdf[field] field = "Colormap" # Create GeoJSON DataSource for Plot: if type(gdf) != pd.DataFrame: geo_source = GeoJSONDataSource(geojson=gdf.to_json()) else: geo_source = gdf # Draw Glyph on Figure: layout = None if "Point" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] glyph = p.scatter( x="x", y="y", source=geo_source, legend_label=legend, **kwargs ) if "Line" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] del kwargs["fill_color"] glyph = p.multi_line( xs="xs", ys="ys", source=geo_source, legend_label=legend, **kwargs ) if "Polygon" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = "black" # Creates from a geoDataFrame with Polygons and Multipolygons a Pandas DataFrame # with x any y columns specifying the geometry of the Polygons: geo_source = ColumnDataSource( convert_geoDataFrame_to_patches(gdf, geometry_column) ) # Plot polygons: glyph = p.multi_polygons( xs="__x__", ys="__y__", source=geo_source, legend_label=legend, **kwargs ) # Add hovertool: if hovertool and (category_options == 1 or len(hovertool_columns) > 0): my_hover = HoverTool(renderers=[glyph]) if hovertool_string is None: my_hover.tooltips = [(str(col), "@{%s}" % col) for col in hovertool_columns] else: my_hover.tooltips = hovertool_string p.add_tools(my_hover) # Add colorbar: if show_colorbar and category_options == 1: colorbar_options = { "color_mapper": colormapper, "label_standoff": 12, "border_line_color": None, "location": (0, 0), } if colormap_uselog: colorbar_options["ticker"] = LogTicker() if colorbar_tick_format: colorbar_options["formatter"] = get_tick_formatter(colorbar_tick_format) colorbar = ColorBar(**colorbar_options) p.add_layout(colorbar, "right") # Add Dropdown Widget: if not dropdown is None: # Define Dropdown widget: dropdown_widget = Select( title="Select Choropleth Layer", options=list(zip(dropdown, dropdown)) ) # Define Callback for Dropdown widget: callback = CustomJS( args=dict( dropdown_widget=dropdown_widget, geo_source=geo_source, legend=p.legend[0].items[0], ), code=""" //Change selection of field for Colormapper for choropleth plot: geo_source.data["Colormap"] = geo_source.data[dropdown_widget.value]; geo_source.change.emit(); //Change label of Legend: legend.label["value"] = " " + dropdown_widget.value; """, ) dropdown_widget.js_on_change("value", callback) # Add Dropdown widget above the plot: if old_layout is None: layout = column(dropdown_widget, p) else: layout = column(dropdown_widget, old_layout) # Add Slider Widget: if not slider is None: if slider_range is None: slider_start = 0 slider_end = len(slider) - 1 slider_step = 1 value2name = ColumnDataSource( { "Values": np.arange( slider_start, slider_end + slider_step, slider_step ), "Names": slider, } ) # Define Slider widget: slider_widget = Slider( start=slider_start, end=slider_end, value=slider_start, step=slider_step, title=slider_name, ) # Define Callback for Slider widget: callback = CustomJS( args=dict( slider_widget=slider_widget, geo_source=geo_source, value2name=value2name, ), code=""" //Change selection of field for Colormapper for choropleth plot: var slider_value = slider_widget.value; var i; for(i=0; i<value2name.data["Names"].length; i++) { if (value2name.data["Values"][i] == slider_value) { var name = value2name.data["Names"][i]; } } geo_source.data["Colormap"] = geo_source.data[name]; geo_source.change.emit(); """, ) slider_widget.js_on_change("value", callback) # Add Slider widget above the plot: if old_layout is None: layout = column(slider_widget, p) else: layout = column(slider_widget, old_layout) # Hide legend if user wants: if legend_input is False: p.legend.visible = False # Set click policy for legend: p.legend.click_policy = "hide" # Set panning option: if panning is False: p.toolbar.active_drag = None # Set zooming option: if zooming is False: p.toolbar.active_scroll = None # Display plot and if wanted return plot: if layout is None: if old_layout is None: layout = p else: layout = old_layout # Display plot if wanted if show_figure: show(layout) # Return as (embeddable) HTML if wanted: if return_html: return embedded_html(layout) # Return plot: if return_figure: return layout
offset=offset), code=""" const data = source.data; const A = amp.value; const k = freq.value; const phi = phase.value; const B = offset.value; const x = data['x'] const y = data['y'] for (let i = 0; i < x.length; i++) { y[i] = B + A*Math.sin(k*x[i]+phi); } source.change.emit(); """) amp.js_on_change('value', callback) freq.js_on_change('value', callback) phase.js_on_change('value', callback) offset.js_on_change('value', callback) heading = Div( sizing_mode="stretch_width", height=80, text="In this wave example, the sliders on the left " "can be used to change the amplitude, frequency, phase, and offset of the wave." ) layout = column(heading, row(widgets, plot), sizing_mode="stretch_both") output_file("slider.html", title="slider.py example")
def addGenomeTools(fig, geneRecs, genomeRecs, geneSource, genomeSource, nb, geneLabels): # add genome labels genomeLabels = LabelSet(x='x_label', y='y', x_offset=-20, text='name', text_align="right", source=genomeSource, render_mode='canvas', text_font_size="16px") fig.add_layout(genomeLabels) slider_font = Slider(start=0, end=64, value=16, step=1, title="Genome label font size in px") slider_font.js_on_change( 'value', CustomJS(args=dict(other=genomeLabels), code="other.text_font_size = this.value+'px';")) slider_offset = Slider(start=-400, end=0, value=-20, step=1, title="Genome label offset") slider_offset.js_link('value', genomeLabels, 'x_offset') slider_spacing = Slider(start=1, end=40, value=10, step=1, title="Genomes spacing") slider_spacing.js_on_change( 'value', CustomJS(args=dict(geneRecs=geneRecs, geneSource=geneSource, genomeRecs=genomeRecs, genomeSource=genomeSource, nb_elements=nb, genomeLabels=genomeLabels, geneLabels=geneLabels), code=""" var current_val = genomeSource.data['y'][genomeSource.data['y'].length - 1] / (nb_elements-1); for (let i=0 ; i < genomeSource.data['y'].length ; i++){ genomeSource.data['y'][i] = (genomeSource.data['y'][i] * this.value) / current_val; } for (let i=0 ; i < geneSource.data['y'].length ; i++){ if((geneSource.data['ordered'][i] == 'True' && geneSource.data['strand'][i] == '+') || (geneSource.data['ordered'][i] == 'False' && geneSource.data['strand'][i] == '-') ){ geneSource.data['y'][i] = (((geneSource.data['y'][i]-1) * this.value) / current_val) +1; geneSource.data['y_label'][i] = (((geneSource.data['y_label'][i]-1-1.5) * this.value) / current_val) + 1 + 1.5; }else{ geneSource.data['y'][i] = (((geneSource.data['y'][i]+1) * this.value) / current_val) -1; geneSource.data['y_label'][i] = (((geneSource.data['y_label'][i]+1-1.5) * this.value) / current_val) -1 + 1.5; } } geneRecs.source = geneSource; genomeRecs.source = genomeSource; geneLabels.source = geneSource; genomeLabels.source = genomeSource; geneSource.change.emit(); genomeSource.change.emit(); """)) genome_header = Div(text="<b>Genomes:</b>") return column(genome_header, slider_spacing, slider_font, slider_offset)
for (var i = 0; i < lblx.length; i++) { lbly[i] = lb(lblx[i], VMAX, KM); } for (var i = 0; i < lbpx.length; i++) { lbpy[i] = lb(lbpx[i], VMAX, KM); } // change data sources mmLineSource.change.emit(); mmPointSource.change.emit(); lbLineSource.change.emit(); lbPointSource.change.emit(); """) # add sliders to plot and display vmax_slider.js_on_change('value', callback) km_slider.js_on_change('value', callback) # ------------------------------------------------- # create baseline michaelis-menten plot # ------------------------------------------------- layout = row( mm_plot, lb_plot, column(vmax_slider, km_slider), ) output_file(html_output_dir + "04-dual-lb-mm-plot.html", title="Michaelis-Menten Kinetics") show(layout)
def plot_surfaces_side_by_side(fig_kwargs=None): sample_size = 10 _x = x[:sample_size] _y = y[:sample_size] m_hat = ((_y.sum() * _x.sum() / sample_size - (_y * _x).sum()) / ((_x.sum())**2 / sample_size - (_x**2).sum())) k_hat = _y.sum() / sample_size - m_hat * _x.sum() / sample_size fit_source = ColumnDataSource({"slope": [m_hat], "intercept": [k_hat]}) figures = [] fig_kwargs = fig_kwargs or {} for sample_size in [None, sample_size]: width = fig_kwargs.get("width", 400) if sample_size is not None: width += 200 fig_kwargs["width"] = width p = _init_surface_plot(sample_size, fig_kwargs) source, mapper = _compute_cost_surface(sample_size) if sample_size is None: global_mapper = mapper p.image("log", x="x", y="y", dw="dw", dh="dh", color_mapper=global_mapper, source=source, name="img", level="image") r = p.plus([b_star], [m_star], line_color="#000000", line_width=0.8, fill_color="#ffffff", size=10) items = [LegendItem(renderers=[r], label="True best parameters")] r = p.plus("intercept", "slope", line_color="#000000", line_width=1.5, fill_color="#000000", size=10, source=fit_source) items.append(LegendItem(renderers=[r], label="Best fit on sample")) p.add_tools( HoverTool(renderers=p.select("img"), tooltips=[("Slope", "$y"), ("Intercept", "$x"), ("Expected Error", "@img")])) figures.append(p) p_true, p_emp = figures p_emp.add_layout(Legend(items=items), "right") js_code = """ var img_data = img_src.data; // data from the source containing the image var sample_data = sample_src.data; // data from the source containing our samples var n = cb_obj.value; // current value of the slider var img_dim = {}; // format the string with the grid dimension // initialize all the variables we'll need var pixel; var slope; var intercept; var error; var idx; // loop through each slope/intercept combination, calculate // the average error over the current number of samples, then // update that pixel in the image source's data for (var i = 0; i < img_dim; i++) {{ for (var j = 0; j < img_dim; j++) {{ pixel = 0; idx = i*img_dim + j; for (var k = 0; k < n; k++) {{ slope = img_data["slope"][0][idx] intercept = img_data["intercept"][0][idx] error = slope*sample_data["x"][k] + intercept - sample_data["y"][k]; pixel += Math.pow(error, 2); }} pixel /= n; img_data["log"][0][idx] = Math.log(pixel); img_data["img"][0][idx] = pixel; }} }} // have the source update all its renderers to reflect the new data img_src.change.emit(); // now do our regression fit in JS land to // move the best fit cross around the screen var fit_data = fit_src.data; // push all n samples to our subsample source // compute sums we'll need to do regression fit var x; var y; var xsum = 0; var ysum = 0; var x2sum = 0; var xysum = 0; for (var i = 0; i < n; i ++) {{ x = sample_data["x"][i]; y = sample_data["y"][i]; xsum += x; ysum += y; x2sum += Math.pow(x, 2); xysum += x*y; }} // update best fit values for regression var denom = n*x2sum - Math.pow(xsum, 2); var m = (n*xysum - xsum*ysum) / denom; var b = (ysum*x2sum - xsum*xysum) / denom; fit_data["intercept"] = [b]; fit_data["slope"] = [m]; // update all source renderers fit_src.change.emit(); """.format(source.data["img"][0].shape[0]) slider = Slider(start=1, end=N, step=1, value=sample_size, title="Observations Used for Fit", orientation="horizontal", direction="ltr") callback = CustomJS(args={ "img_src": source, "sample_src": ColumnDataSource(data), "fit_src": fit_source }, code=js_code) slider.js_on_change("value", callback) return column(row(*figures), slider)
return (bottom + ((top - bottom) / (1 + Math.pow(10, ((ec50 + x) * hillslope * -1))))); } // loop over data and edit for (var i = 0; i < lx.length; i++) { ly[i] = fpl(lx[i], TOP, BOTTOM, EC50, HILL); } for (var i = 0; i < px.length; i++) { py[i] = fpl(px[i], TOP, BOTTOM, EC50, HILL); } // emit changes LineSource.change.emit(); PointSource.change.emit(); """) # add sliders to plot and display top_slider.js_on_change('value', callback) bottom_slider.js_on_change('value', callback) ec50_slider.js_on_change('value', callback) hill_slider.js_on_change('value', callback) layout = row( plot, column(top_slider, bottom_slider, ec50_slider, hill_slider), ) output_file(html_output_dir + "10-receptors-dr.html", title="Dose Response") show(layout)
const lx = LineData['x'] const ly = LineData['y'] const px = PointData['x'] const py = PointData['y'] for (var i = 0; i < lx.length; i++) { ly[i] = (VMAX*lx[i])/((KM*(1+(CI/KI))+lx[i])); } LineSource.change.emit(); for (var i = 0; i < px.length; i++) { py[i] = (VMAX*px[i])/((KM*(1+(CI/KI))+px[i])); } PointSource.change.emit(); """) # add sliders to plot and display ci_slider.js_on_change('value', callback) ki_slider.js_on_change('value', callback) layout = row( plot, column(ci_slider, ki_slider), ) show(layout) # ---------------------------------------------------------------------------------------------------------------------- # noncompetitive inhibition # ---------------------------------------------------------------------------------------------------------------------- # generate data for plotting log_start = -1 log_end = 4
freq=freq_slider, phase=phase_slider, offset=offset_slider), code=""" const data = source.data; const A = amp.value; const k = freq.value; const phi = phase.value; const B = offset.value; const x = data['x'] const y = data['y'] for (let i = 0; i < x.length; i++) { y[i] = B + A*Math.sin(k*x[i]+phi); } source.change.emit(); """) amp_slider.js_on_change('value', callback) freq_slider.js_on_change('value', callback) phase_slider.js_on_change('value', callback) offset_slider.js_on_change('value', callback) layout = row( plot, column(amp_slider, freq_slider, phase_slider, offset_slider), ) output_file("slider.html", title="slider.py example") show(layout)
class MyGraph: def __init__(self): self._graph_layout = side_pos self.slider = Slider(start=1, end=60, value=7, step=1, title="X Zoom") self.plot = bokeh.plotting.figure(plot_width=400, plot_height=400, x_range=Range1d(-1, -1 + self.slider.value, bounds=(-2, 4e6)), y_range=Range1d(0, 1, bounds=(-0.5, 1.5)), tools='') self.slider.js_on_change( 'value', CustomJS(args=dict(slider=self.slider, plot=self.plot), code=''' var days = slider.value; plot.x_range.end=plot.x_range.start + days; plot.x_range.change.emit(); ''')) self.xSelectSwitch = RadioButtonGroup( labels=['By Generation', 'By Date'], active=0) self.plot.toolbar.logo = None self.plot.title.text = "Samples" self.plot.yaxis.visible = False self.plot.ygrid.visible = False self.plot.xaxis[0].ticker.min_interval = 1 self.plot.xaxis[0].ticker.num_minor_ticks = 0 self.plot.xaxis[0].axis_label = 'Generations' self.pallete = bokeh.palettes.Spectral4 tools = [TapTool(), WheelZoomTool(), PanTool(), ResetTool()] # HoverTool(tooltips={'ID':"@id",'Type':'@type','Notes':'@notes'})] tools[1].dimensions = 'height' # vertical zoom only self.plot.add_tools(*tools) self.plot.toolbar.active_scroll = tools[ 1] # sets the scroll zoom to be active immediately. self.colors = ['red', 'blue', 'green', 'purple', 'cyan', 'yellow'] self.widget = Column(self.plot, self.slider, self.xSelectSwitch) def getFromDB(self, sqlsession): self._graph = nx.DiGraph() self.nodelist = [(i.id, i.toJSON()) for i in sqlsession.query(Sample).all()] '''Generate Networkx graph''' self._graph.add_nodes_from(self.nodelist) edges = [(i[0], j) for i in self.nodelist for j in i[1]['children']] self._graph.add_edges_from(edges) '''Load nx graph to bokeh renderer''' self.renderer: bokeh.models.GraphRenderer = bokeh.plotting.from_networkx( self._graph, self._graph_layout) # use the first item in the nodelist to generate the possible data sources for the hover tool for k, v in self.nodelist[0][1].items(): self.renderer.node_renderer.data_source.add( [i[1][k] for i in self.nodelist], name=k) import pdb pdb.set_trace() self.renderer.node_renderer.data_source.add( # Set color based on sampletype [ self.colors[Sample.Type[nodeData['type']].value - 1] for nodeId, nodeData in self.nodelist ], 'color') self.renderer.node_renderer.glyph = bokeh.models.Circle( size=20, fill_color='color') # self.pallete[0]) self.renderer.node_renderer.selection_glyph = bokeh.models.Circle( size=15, fill_color='color') self.renderer.node_renderer.hover_glyph = bokeh.models.Circle( size=15, fill_color=self.pallete[1]) self.renderer.edge_renderer.glyph = bokeh.models.MultiLine( line_color="#CCCCCC", line_alpha=0.8, line_width=5) self.renderer.edge_renderer.selection_glyph = bokeh.models.MultiLine( line_color=self.pallete[0], line_width=5) self.renderer.edge_renderer.hover_glyph = bokeh.models.MultiLine( line_color=self.pallete[1], line_width=5) self.renderer.selection_policy = bokeh.models.graphs.NodesAndLinkedEdges( ) self.renderer.inspection_policy = bokeh.models.graphs.NodesOnly() try: oldrenderer = \ [(i, v) for i, v in enumerate(self.plot.renderers) if isinstance(v, bokeh.models.GraphRenderer)][0] oldsel = oldrenderer[1].node_renderer.data_source.selected except: oldsel = None oldrenderer = None if oldrenderer: self.plot.renderers.pop(oldrenderer[0]) self.plot.renderers.append(self.renderer) # If something was previously selected add it reselect it now if oldsel: self.renderer.node_renderer.data_source.selected.indices = oldsel.indices
def _get_widgets(self, all_glyphs, overtime_groups, run_groups, slider_labels=None): """Combine timeslider for quantiles and checkboxes for individual runs in a single javascript-snippet Parameters ---------- all_glyphs: List[Glyph] togglable bokeh-glyphs overtime_groups, run_groups: Dicŧ[str -> List[int] mapping labels to indices of the all_glyphs-list slider_labels: Union[None, List[str]] if provided, used as labels for timeslider-widget Returns ------- time_slider, checkbox, select_all, select_none: Widget desired interlayed bokeh-widgets checkbox_title: Div text-element to "show title" of checkbox """ aliases = ['glyph' + str(idx) for idx, _ in enumerate(all_glyphs)] labels_overtime = list(overtime_groups.keys()) labels_runs = list(run_groups.keys()) code = "" # Define javascript variable with important arrays code += "var glyphs = [" + ", ".join(aliases) + "];" code += "var overtime = [" + ','.join([ '[' + ','.join(overtime_groups[l]) + ']' for l in labels_overtime ]) + '];' code += "var runs = [" + ','.join( ['[' + ','.join(run_groups[l]) + ']' for l in labels_runs]) + '];' # Deactivate all glyphs code += """ glyphs.forEach(function(g) { g.visible = false; })""" # Add function for array-union (to combine all relevant glyphs for the different runs) code += """ // union function function union_arrays(x, y) { var obj = {}; for (var i = x.length-1; i >= 0; -- i) obj[x[i]] = x[i]; for (var i = y.length-1; i >= 0; -- i) obj[y[i]] = y[i]; var res = [] for (var k in obj) { if (obj.hasOwnProperty(k)) // <-- optional res.push(obj[k]); } return res; }""" # Add logging code += """ console.log("Timeslider: " + time_slider.value); console.log("Checkbox: " + checkbox.active);""" # Set timeslider title (to enable log-scale and print wallclocktime-labels) if slider_labels: code += "var slider_labels = " + str(slider_labels) + ";" code += "console.log(\"Detected slider_labels: \" + slider_labels);" code += "time_slider.title = \"Until wallclocktime \" + slider_labels[time_slider.value - 1] + \". Step no.\"; " title = "Until wallclocktime " + slider_labels[-1] + ". Step no. " else: title = "Quantile on {} scale".format( "logarithmic" if self.timeslider_log else "linear") code += "time_slider.title = \"{}\";".format(title) # Combine checkbox-arrays, intersect with time_slider and set all selected glyphs to true code += """ var activate = []; // if we want multiple checkboxes at the same time, we need to combine the arrays checkbox.active.forEach(function(c) { activate = union_arrays(activate, runs[c]); }) // now the intersection of timeslider-activated and checkbox-activated activate = activate.filter(value => -1 !== overtime[time_slider.value - 1].indexOf(value)); activate.forEach(function(idx) { glyphs[idx].visible = true; }) """ num_quantiles = len(overtime_groups) if num_quantiles > 1: timeslider = Slider(start=1, end=num_quantiles, value=num_quantiles, step=1, title=title) else: timeslider = Slider(start=1, end=2, value=1) labels_runs = [ label.replace('_', ' ') if label.startswith('budget') else label for label in labels_runs ] checkbox = CheckboxButtonGroup(labels=labels_runs, active=list(range(len(labels_runs)))) args = {name: glyph for name, glyph in zip(aliases, all_glyphs)} args['time_slider'] = timeslider args['checkbox'] = checkbox callback = CustomJS(args=args, code=code) timeslider.js_on_change('value', callback) checkbox.callback = callback checkbox_title = Div(text="Showing only configurations evaluated in:") # Add all/none button to checkbox code_all = "checkbox.active = " + str(list(range( len(labels_runs)))) + ";" + code code_none = "checkbox.active = [];" + code select_all = Button(label="All", callback=CustomJS(args=args, code=code_all)) select_none = Button(label="None", callback=CustomJS(args=args, code=code_none)) return timeslider, checkbox, select_all, select_none, checkbox_title
def roi_plot(roi, *args, **kwargs): from tramway.helper import map_plot from .map import scalar_map_2d, plot_points feature_name = kwargs.pop("feature") kwargs.update(dict(use_bokeh=True, show=False)) if "point_style" not in kwargs: kwargs["point_style"] = dict(color="r", alpha=0.1) fig = map_plot(*args, **kwargs) full_fov_fig = fig[0] patches_x, patches_y = [], [] for center, bounding_box in roi: patch_x = bounding_box[[0, 0, 2, 2]] patch_y = bounding_box[[1, 3, 3, 1]] patches_x.append(patch_x) patches_y.append(patch_y) roi_controller = RoiController(patches_x, patches_y, fill_color=None) full_fov_fig.patches(**roi_controller.patches_kwargs) roi_controller.unset_active("all") first_roi = 0 roi_controller.set_active(first_roi) # right panel first_roi_bb = roi[first_roi][1] xlim = first_roi_bb[[0, 2]].tolist() ylim = first_roi_bb[[1, 3]].tolist() from bokeh.plotting import figure, show analysis_tree = args[0] partition_label, map_label = kwargs["label"] cells = analysis_tree[partition_label].data _map = analysis_tree[partition_label][map_label].data[feature_name] _kwargs = {} for _attr in ("clim", ): if _attr in kwargs: _kwargs[attr] = kwargs[attr] focused_fig = figure() scalar_map_2d(cells, _map, figure=focused_fig, xlim=xlim, ylim=xlim, **_kwargs) plot_points(cells, color="r", alpha=0.1) from bokeh.models import Slider, CustomJS slider = Slider(start=1, end=len(roi), step=1, value=1, title="roi") slider.js_on_change("value", roi_controller.js_callback(focused_fig)) # plot with layout from bokeh.layouts import row, column show( row(full_fov_fig, column(focused_fig, slider, sizing_mode="scale_width")))
scR[0] = R[dia[0]]; var j = 0 for (var i = ( len * (dia[0] - start)); i < ( len * (dia[0] - start) + len); i++) { Sp[j] = lSp[i] Ip[j] = lIp[i] Rp[j] = lRp[i] j++ } s1.change.emit(); s2.change.emit(); """) dias_slider.js_on_change('value', callback) plot.legend.location = "top_left" if BOKEH_THEME == 'Dark': save_name = './slider' + COUNTRY + 'dark.html' my_theme = define_custom_theme('#2C2F38','#2C2F38') plot.legend.background_fill_alpha = 0.0 plot.legend.border_line_alpha = 0 plot.legend.label_text_color = 'white' plot.xaxis.major_tick_line_color = None # turn off x-axis major ticks plot.xaxis.minor_tick_line_color = None # turn off x-axis minor ticks plot.yaxis.major_tick_line_color = None # turn off y-axis major ticks plot.yaxis.minor_tick_line_color = None # turn off y-axis minor ticks elif BOKEH_THEME == 'Light': save_name = './slider' + COUNTRY + '.html'
y[i] = Math.pow(x[i], f) } source.change.emit(); """) callback_ion = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.range var x = data['x'] var y = data['y'] var pow = (Math.log(y[100])/Math.log(x[100])) console.log(pow) var delta = (f[1] - f[0])/x.length for (var i = 0; i < x.length; i++) { x[i] = delta*i + f[0] y[i] = Math.pow(x[i], pow) } source.change.emit(); """) slider = Slider(start=0, end=5, step=0.1, value=1, title="Bokeh Slider - Power") slider.js_on_change('value', callback_single) ion_range_slider = IonRangeSlider(start=0.01, end=0.99, step=0.01, range=(min(x), max(x)), title='Ion Range Slider - Range', callback_policy='continuous', callback=callback_ion) layout = column(plot, slider, ion_range_slider) show(layout)
lines''' ] def xs(cat): return [(cat, -0.3), (cat, -0.1), (cat, 0.1), (cat, 0.3)] def ys(cat): return [cat] * 4 renderers = {} i = 0 for a in aligns: for b in baselines: r = p.text(xs(a), ys(b), texts, text_align=a, text_baseline=b, text_font_size="14px", text_line_height=1.2) renderers["r" + str(i)] = r i += 1 slider = Slider(title="Text Angle", start=0, end=45, step=1, value=0) slider.js_on_change('value', CustomJS(args=renderers, code=""" var rs = [r0, r1, r2 , r3, r4, r5, r6, r7, r8]; for (var i = 0; i < 9; i++) { rs[i].glyph.angle = {value: cb_obj.value, units: "deg"} } """)) output_file("text.html") show(column(p, slider))
def Rips_Filter_Bifiltration(filtered_points, radius_range, palette="Viridis256", FilterName="Filter", maxind: int = None, dim: int = None): if maxind is None: maxind = 5 if dim is None: dim = 0 points = filtered_points[:, :2] filter = filtered_points[:, 2] alpha = np.ones(filter.shape) * 0.3 exp_cmap = LinearColorMapper(palette=palette, low=radius_range[0], high=radius_range[1]) source = ColumnDataSource( data=dict(x=points[:, 0], y=points[:, 1], sizes=(radius_range[0] + radius_range[1]) / 4 * np.ones(points.shape[0]), filter=filter, alpha=alpha)) vline = ColumnDataSource( data=dict(c=[radius_range[0]], y=[0], angle=[np.pi / 2])) hline = ColumnDataSource(data=dict(s=[radius_range[0]], x=[0], angle=[0])) filter_plot = figure(title='Filtration', plot_width=360, plot_height=430, min_border=0, toolbar_location=None, match_aspect=True) glyph = Circle(x="x", y="y", radius="sizes", line_color="black", fill_color={ 'field': 'filter', 'transform': exp_cmap }, fill_alpha="alpha", line_width=1, line_alpha="alpha") filter_plot.add_glyph(source, glyph) filter_plot.add_layout( ColorBar(color_mapper=exp_cmap, location=(0, 0), orientation="horizontal"), "below") rips_callback = CustomJS(args=dict(source=source, hline=hline), code=""" var data = source.data; var s = cb_obj.value var sizes = data['sizes'] for (var i = 0; i < sizes.length; i++) { sizes[i] = s/2 } var hdata = hline.data; var step = hdata['s'] step[0] = s hline.change.emit(); source.change.emit(); """) filter_callback = CustomJS(args=dict(source=source, vline=vline), code=""" var data = source.data; var c = cb_obj.value var alpha = data['alpha'] var filter = data['filter'] for (var i = 0; i<filter.length; i++){ if(filter[i]>c){ alpha[i] = 0 } if(filter[i]<=c){ alpha[i] = 0.3 } } var vdata = vline.data; var step = vdata['c'] step[0] = c vline.change.emit(); source.change.emit(); """) rips_slider = Slider(start=radius_range[0], end=radius_range[1], value=(radius_range[0] + radius_range[1]) / 2, step=(radius_range[1] - radius_range[0]) / 100, title="Rips", orientation="vertical", height=300, direction="rtl", margin=(10, 40, 10, 60)) rips_slider.js_on_change('value', rips_callback) filter_slider = Slider(start=radius_range[0], end=radius_range[1], value=radius_range[1], step=(radius_range[1] - radius_range[0]) / 100, title=FilterName, orientation="horizontal", aspect_ratio=10, width_policy="auto", direction="ltr", width=300, margin=(10, 10, 10, 40)) filter_slider.js_on_change('value', filter_callback) # Run Rivet and Landscape Computation # computed_data = Compute_Rivet(filtered_points, resolution=50, dim=dim, RipsMax=radius_range[1]) multi_landscape = multiparameter_landscape( computed_data, grid_step_size=(radius_range[1] - radius_range[0]) / 100, bounds=[[radius_range[0], radius_range[0]], [radius_range[1], radius_range[1]]], maxind=maxind) TOOLTIPS = [(FilterName, "$x"), ("Radius", "$y"), ("Landscape_Value", "@image")] landscape_plots = plot_multiparameter_landscapes( multi_landscape, indices=[1, maxind], TOOLTIPS=TOOLTIPS, x_axis_label=FilterName, y_axis_label="Rips Parameter") for plot in landscape_plots.children: plot.ray(x="c", y="y", length="y", angle="angle", source=vline, color='white', line_width=2, alpha=0.5) plot.ray(x="x", y="s", length="x", angle="angle", source=hline, color='white', line_width=2, alpha=0.5) layout = column(row(rips_slider, column(filter_plot, filter_slider)), row(landscape_plots), sizing_mode="scale_both") return layout
This example demonstrates how ``CustomJS`` callbacks react to user interaction events. .. bokeh-example-metadata:: :apis: bokeh.layouts.column, bokeh.models.callbacks.CustomJS :refs: :ref:`userguide_interaction_jscallbacks` > :ref:`userguide_interaction_jscallbacks_customjs_interactions` :keywords: javascript callback ''' from bokeh.io import output_file, show from bokeh.layouts import column from bokeh.models import CustomJS, Div, Slider para = Div( text="<h1>Slider Values:</h1><p>Slider 1: 0<p>Slider 2: 0<p>Slider 3: 0") s1 = Slider(title="Slider 1 (Continuous)", start=0, end=1000, value=0, step=1) s2 = Slider(title="Slider 3 (Mouse Up)", start=0, end=1000, value=0, step=1) callback = CustomJS(args=dict(para=para, s1=s1, s2=s2), code=""" para.text = "<h1>Slider Values</h1><p>Slider 1: " + s1.value + "<p>Slider 2: " + s2.value """) s1.js_on_change('value', callback) s2.js_on_change('value_throttled', callback) output_file('slider_callback_policy.html') show(column(s1, s2, para))
def Rips_Filtration(points, radius_range): rips = Rips(maxdim=1, thresh=radius_range[1], verbose=False) barcodes = rips.transform(points) H_0_Bars = barcodes[0] H_1_Bars = barcodes[1] # Plotting Parameters H_0_color = '#1d07ad' H_1_color = '#009655' vertical_line_color = "#FB8072" bar_line_width = 2 circle_opacity = 0.2 circle_color = "green" circle_line_color = "black" source = ColumnDataSource(data=dict(x=points[:, 0], y=points[:, 1], sizes=radius_range[0] / 2 * np.ones(points.shape[0]), ) ) vline = ColumnDataSource(data=dict(s=[radius_range[0]], y=[0], angle=[np.pi / 2] ) ) filt_plot = figure(title='Filtration', plot_width=300, plot_height=300, aspect_ratio="auto", height_policy="auto", min_border=0, toolbar_location=None, match_aspect=True) glyph = Circle(x="x", y="y", radius="sizes", line_color=circle_line_color, fill_color=circle_color, fill_alpha=circle_opacity, line_width=1) filt_plot.add_glyph(source, glyph) callback = CustomJS(args=dict(source=source, vline=vline), code=""" var data = source.data; var s = cb_obj.value var sizes = data['sizes'] for (var i = 0; i < sizes.length; i++) { sizes[i] = s/2 } var vdata = vline.data; var step = vdata['s'] step[0] = s vline.change.emit(); source.change.emit(); """) barcode_plot = figure(title='Barcode', plot_width=800, plot_height=200, min_border=0, aspect_ratio="auto", height_policy="auto", toolbar_location=None, x_axis_label='Filtration Value', x_range=(radius_range[0], radius_range[1])) lscape_plot = figure(title='Landscapes', plot_width=800, plot_height=200, min_border=0, aspect_ratio="auto", height_policy="auto", toolbar_location=None, x_axis_label='Filtration Value', x_range=(radius_range[0], radius_range[1])) for bar in range(len(H_0_Bars)): if H_0_Bars[bar, 1] < radius_range[1]: barcode_plot.line([H_0_Bars[bar, 0], H_0_Bars[bar, 1]], [bar / len(H_0_Bars), bar / len(H_0_Bars)], legend_label='H0 Bars', color=H_0_color, line_width=bar_line_width) else: barcode_plot.line([H_0_Bars[bar, 0], radius_range[1]], [bar / len(H_0_Bars), bar / len(H_0_Bars)], legend_label='H0 Bars', color=H_0_color, line_width=bar_line_width) for bar in range(len(H_1_Bars)): if H_1_Bars[bar, 1] < radius_range[1]: barcode_plot.line([H_1_Bars[bar, 0], H_1_Bars[bar, 1]], [3 / 2 + bar / len(H_1_Bars), 3 / 2 + bar / len(H_1_Bars)], legend_label='H1 Bars', color=H_1_color, line_width=bar_line_width) else: barcode_plot.line([H_1_Bars[bar, 0], radius_range[1]], [3 / 2 + bar / len(H_1_Bars), 3 / 2 + bar / len(H_1_Bars)], legend_label='H1 Bars', color=H_1_color, line_width=bar_line_width) barcode_plot.ray(x="s", y="y", length="y", angle="angle", source=vline, color="#FB8072", line_width=bar_line_width) barcode_plot.yaxis.major_tick_line_color = None # turn off y-axis major ticks barcode_plot.yaxis.minor_tick_line_color = None # turn off y-axis minor ticks barcode_plot.yaxis.major_label_text_font_size = '0pt' # preferred method for removing tick labels barcode_plot.legend.location = "bottom_right" H0rivet_barcode = ripser_to_rivet_bcode(H_0_Bars) L = compute_landscapes(H0rivet_barcode) for k in range(len(L.landscapes)): n = np.shape(L.landscapes[k].critical_points)[0] x = L.landscapes[k].critical_points[1:n, 0] y = L.landscapes[k].critical_points[1:n, 1] if k < 2: lscape_plot.line(x=x, y=y, color=H_0_color, line_alpha=1 / (k + 1), line_width=bar_line_width, legend_label='H0: k =' + str(k + 2), muted_color=vertical_line_color, muted_alpha=1) else: lscape_plot.line(x=x, y=y, color=H_0_color, line_alpha=1 / (k + 1), line_width=bar_line_width) H_1_rivet_barcode = ripser_to_rivet_bcode(H_1_Bars) L = compute_landscapes(H_1_rivet_barcode) for k in range(len(L.landscapes)): n = np.shape(L.landscapes[k].critical_points)[0] x = L.landscapes[k].critical_points[1:n, 0] y = L.landscapes[k].critical_points[1:n, 1] if k < 2: lscape_plot.line(x=x, y=y, color=H_1_color, line_alpha=1 / (k + 1), line_width=bar_line_width, legend_label='H1: k =' + str(k + 1), muted_color=vertical_line_color, muted_alpha=1) else: lscape_plot.line(x=x, y=y, color=H_1_color, line_alpha=1 / (k + 1), line_width=bar_line_width) lscape_plot.legend.location = "top_right" lscape_plot.legend.click_policy = "mute" lscape_plot.ray(x="s", y="y", length="y", angle="angle", source=vline, color=vertical_line_color, line_width=bar_line_width) slider = Slider(start=radius_range[0], end=radius_range[1], value=radius_range[0], step=(radius_range[1] - radius_range[0]) / 100, title="Rips Parameter", aspect_ratio=20) slider.js_on_change('value', callback) layout = column(row(filt_plot), slider, barcode_plot, lscape_plot, sizing_mode="scale_both") return layout
def get_dashboard(local_data=False): """ Assemble the dashboard. Define the layout, controls and its callbacks, as well as data sources. """ # Load data video_data = {} if local_data: data = pd.json_normalize( pd.read_json('data/channel/processed_data.json')['items']) with open('data/channel/processed_data.json', 'r') as f: data = json.load(f) video_data = data['items'] else: video_data = handle_channel_data.get_data_firebase()['items'] # X axis categories x_axis_map = { "Climber": "climber", "Zone": "zone", "Grade": "grade", } # Y axis categories y_axis_map = { "Count": "count", "Views": "viewCount", # "Favourites": "favoriteCount", "Likes": "likeCount", "Dislikes": "dislikeCount", "Comments": "commentCount" } # get ready to plot data barchart_data = prepare_barchart_data(video_data, x_axis_map) # html template to place the plots desc = Div(text=open(join(dirname(__file__), "templates/stats.html")).read(), sizing_mode="stretch_width") # initial data source fill data_to_plot = barchart_data['grade']['raw'] od = collections.OrderedDict( sorted(data_to_plot.items(), key=lambda x: x[0])) x_to_plot = np.array([key for key, _ in od.items()]) y_to_plot = np.array([val['count'] for _, val in od.items()]) source = ColumnDataSource(data=dict(x=x_to_plot, y=y_to_plot)) # initial data x_init = x_to_plot[0:NUM_RESULTS] y_init = y_to_plot[0:NUM_RESULTS] # Create Input controls checkbox_limit_results = CheckboxGroup( labels=["Show only first 50 results"], active=[0]) label_slider = Slider(start=0, end=90, value=90, step=1, title="Label Angle") range_slider = RangeSlider(title="Value Range", start=0, end=max(y_to_plot), value=(0, max(y_to_plot)), step=1) min_year = Slider(title="From", start=2015, end=2020, value=2015, step=1) max_year = Slider(title="To", start=2015, end=2020, value=2020, step=1) sort_order = RadioButtonGroup( labels=["Alphabetically", "Decreasing", "Increasing"], active=0) x_axis = Select(title="X Axis", options=sorted(x_axis_map.keys()), value="Grade") y_axis = Select(title="Y Axis", options=sorted(y_axis_map.keys()), value="Count") checkbox = CheckboxGroup( labels=["Show ratio with respect to number of videos"], active=[]) # show number of categories x_count_source = ColumnDataSource( data=dict(x_count=[len(x_init)], category=[x_axis.value])) columns = [ TableColumn(field="category", title="Category"), TableColumn(field="x_count", title="Count"), ] x_count_data_table = DataTable(source=x_count_source, columns=columns, width=320, height=280) # Generate the actual plot # p = figure(x_range=x_to_plot, y_range=(0, max(y_to_plot)), plot_height=250, title="{} {}".format(x_axis.value, y_axis.value), # toolbar_location="above") p = figure(x_range=x_init, y_range=(0, max(y_init)), plot_height=250, title="{} {}".format(x_axis.value, y_axis.value), toolbar_location="above") # Fill it with data and format it p.vbar(x='x', top='y', width=0.9, source=source) p.xaxis.major_label_orientation = math.pi / 2 p.add_tools(HoverTool(tooltips=[("name", "@x"), ("count", "@y")])) # Controls controls = [ checkbox_limit_results, range_slider, min_year, max_year, sort_order, x_axis, y_axis, checkbox, label_slider, x_count_data_table ] # Callbacks for controls label_callback = CustomJS(args=dict(axis=p.xaxis[0]), code=""" axis.major_label_orientation = cb_obj.value * Math.PI / 180; """) label_slider.js_on_change('value', label_callback) # limit checkbox checkbox_limit_results_callback = CustomJS( args=dict(source=source, x_source=x_count_source, o_data=barchart_data, sort_order=sort_order, x_axis_map=x_axis_map, x_axis=x_axis, y_axis_map=y_axis_map, y_axis=y_axis, range_slider=range_slider, checkbox=checkbox, fig=p, title=p.title), code=SORT_FUNCTION + JS_NUM_RESULTS + """ var data = o_data[x_axis_map[x_axis.value]]; var x = data['x']; var y = data['y']; var apply_limit = cb_obj.active.length > 0; var is_ratio = checkbox.active.length > 0; title.text = x_axis.value.concat(" ", y_axis.value); // Sort data var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio); var new_y = []; var new_x = []; var final_x = []; var final_y = []; for (var i = 0; i < x.length; i++) { if (apply_limit) { if(sorted_data[i][1] >= range_slider.value[0] && sorted_data[i][1] <= range_slider.value[1]) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } else { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } if (apply_limit) { final_x = new_x.slice(0, num_results); final_y = new_y.slice(0, num_results); window.should_update_range = false; } else { final_x = new_x; final_y = new_y; window.should_update_range = true; } x_source.data['x_count'] = [final_x.length]; x_source.data['category'] = [x_axis.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = final_x; if (Array.isArray(new_y) && new_y.length) { // range init and end cannot have same value var range_end = Math.max.apply(Math, new_y); if (range_end == 0 || range_end == -Infinity) { range_end = 1; } range_slider.value = [0, Math.max.apply(Math, final_y)]; range_slider.end = range_end; fig.y_range.end = Math.max.apply(Math, final_y); fig.change.emit(); } """) checkbox_limit_results.js_on_change('active', checkbox_limit_results_callback) # ratio checkbox checkbox_callback = CustomJS(args=dict( source=source, x_source=x_count_source, o_data=barchart_data, sort_order=sort_order, x_axis_map=x_axis_map, x_axis=x_axis, y_axis_map=y_axis_map, y_axis=y_axis, range_slider=range_slider, checkbox_limit_results=checkbox_limit_results, fig=p, title=p.title), code=SORT_FUNCTION + JS_NUM_RESULTS + """ var data = o_data[x_axis_map[x_axis.value]]; var x = data['x']; var y = data['y']; var is_ratio = cb_obj.active.length > 0; title.text = x_axis.value.concat(" ", y_axis.value); if (is_ratio) { title.text = x_axis.value.concat(" ", y_axis.value, " per video"); } // Sort data var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio); var new_y = []; var new_x = []; var final_x = []; var final_y = []; for (var i = 0; i < x.length; i++) { if (checkbox_limit_results.active.length <= 0) { if(sorted_data[i][1] >= range_slider.value[0] && sorted_data[i][1] <= range_slider.value[1]) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } else { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } if (checkbox_limit_results.active.length > 0) { final_x = new_x.slice(0, num_results); final_y = new_y.slice(0, num_results); window.should_update_range = false; } else { final_x = new_x; final_y = new_y; window.should_update_range = true; } x_source.data['x_count'] = [final_x.length]; x_source.data['category'] = [x_axis.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = final_x; if (Array.isArray(new_y) && new_y.length) { // range init and end cannot have same value var range_end = Math.max.apply(Math, new_y); if (range_end == 0 || range_end == -Infinity) { range_end = 1; } range_slider.value = [0, Math.max.apply(Math, final_y)]; range_slider.end = range_end; fig.y_range.end = Math.max.apply(Math, final_y); fig.change.emit(); } """) checkbox.js_on_change('active', checkbox_callback) # range slider range_callback = CustomJS(args=dict( source=source, x_source=x_count_source, o_data=barchart_data, sort_order=sort_order, x_axis_map=x_axis_map, x_axis=x_axis, y_axis_map=y_axis_map, y_axis=y_axis, checkbox=checkbox, checkbox_limit_results=checkbox_limit_results, fig=p), code=SORT_FUNCTION + """ if (window.should_update_range == true) { var data = o_data[x_axis_map[x_axis.value]]; var x = data['x']; var y = data['y']; var is_ratio = checkbox.active.length > 0; // Sort data var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio); var new_y = []; var new_x = []; for (var i = 0; i < x.length; i++) { if (checkbox_limit_results.active.length <= 0) { if (sorted_data[i][1] >= cb_obj.value[0] && sorted_data[i][1] <= cb_obj.value[1]) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } else { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } x_source.data['x_count'] = [new_x.length]; x_source.data['category'] = [x_axis.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = new_x; if (Array.isArray(new_y) && new_y.length) { fig.y_range.end = Math.max.apply(Math, new_y); } } else { window.should_update_range = true; } """) range_slider.js_on_change('value', range_callback) # variable to group data x_axis_callback = CustomJS(args=dict( source=source, x_source=x_count_source, o_data=barchart_data, x_axis_map=x_axis_map, y_axis_map=y_axis_map, y_axis=y_axis, range_slider=range_slider, sort_order=sort_order, checkbox=checkbox, checkbox_limit_results=checkbox_limit_results, fig=p, title=p.title), code=SORT_FUNCTION + JS_NUM_RESULTS + """ title.text = cb_obj.value.concat(" ", y_axis.value); var data = o_data[x_axis_map[cb_obj.value]]; var x = data['x']; var y = data['y']; var is_ratio = checkbox.active.length > 0; if (is_ratio) { title.text = title.text.concat(" per video"); } var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio); var new_y = []; var new_x = []; var final_x = []; var final_y = []; for (var i = 0; i < x.length; i++) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } if (checkbox_limit_results.active.length > 0) { final_x = new_x.slice(0, num_results); final_y = new_y.slice(0, num_results); window.should_update_range = false; } else { final_x = new_x; final_y = new_y; window.should_update_range = true; } x_source.data['x_count'] = [final_x.length]; x_source.data['category'] = [cb_obj.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = final_x; if (new_y && Array.isArray(new_y) && new_y.length) { // range init and end cannot have same value var range_end = Math.max.apply(Math, new_y); if (range_end == 0 || range_end == -Infinity) { range_end = 1; } range_slider.value = [0, Math.max.apply(Math, final_y)]; range_slider.end = range_end; fig.y_range.end = Math.max.apply(Math, final_y); fig.change.emit(); } """) x_axis.js_on_change('value', x_axis_callback) # variable to group data y_axis_callback = CustomJS(args=dict( source=source, x_source=x_count_source, o_data=barchart_data, x_axis_map=x_axis_map, x_axis=x_axis, y_axis_map=y_axis_map, range_slider=range_slider, sort_order=sort_order, checkbox=checkbox, checkbox_limit_results=checkbox_limit_results, fig=p, title=p.title), code=SORT_FUNCTION + JS_NUM_RESULTS + """ title.text = x_axis.value.concat(" ", cb_obj.value); var data = o_data[x_axis_map[x_axis.value]]; var x = data['x']; var y = data['y']; var is_ratio = checkbox.active.length > 0; if (is_ratio) { title.text = x_axis.value.concat(" ", cb_obj.value, " per video"); } var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[cb_obj.value], is_ratio); var new_y = []; var new_x = []; var final_x = []; var final_y = []; for (var i = 0; i < x.length; i++) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } if (checkbox_limit_results.active.length > 0) { final_x = new_x.slice(0, num_results); final_y = new_y.slice(0, num_results); window.should_update_range = false; } else { final_x = new_x; final_y = new_y; window.should_update_range = true; } x_source.data['x_count'] = [final_x.length]; x_source.data['category'] = [x_axis.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = final_x; if (new_y && Array.isArray(new_y) && new_y.length) { // range init and end cannot have same value var range_end = Math.max.apply(Math, new_y); if (range_end == 0 || range_end == -Infinity) { range_end = 1; } range_slider.value = [0, Math.max.apply(Math, final_y)]; range_slider.end = range_end; fig.y_range.end = Math.max.apply(Math, final_y); } """) y_axis.js_on_change('value', y_axis_callback) # sort order control sort_order_callback = CustomJS(args=dict( source=source, x_source=x_count_source, o_data=barchart_data, x_axis_map=x_axis_map, x_axis=x_axis, y_axis_map=y_axis_map, y_axis=y_axis, range_slider=range_slider, checkbox=checkbox, checkbox_limit_results=checkbox_limit_results, fig=p), code=SORT_FUNCTION + JS_NUM_RESULTS + """ var data = o_data[x_axis_map[x_axis.value]]; var x = data['x']; var y = data['y']; // Sort data var is_ratio = checkbox.active.length > 0; var sorted_data = sortData(data['raw'], cb_obj.active, y_axis_map[y_axis.value], is_ratio); var new_y = []; var new_x = []; var final_x = []; var final_y = []; // push data if it lies inside range for (var i = 0; i < x.length; i++) { if (checkbox_limit_results.active.length <= 0) { if (sorted_data[i][1] >= cb_obj.value[0] && sorted_data[i][1] <= cb_obj.value[1]) { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } else { new_x.push(sorted_data[i][0]); new_y.push(sorted_data[i][1]); } } if (checkbox_limit_results.active.length > 0) { final_x = new_x.slice(0, 50); final_y = new_y.slice(0, 50); window.should_update_range = false; } else { final_x = new_x; final_y = new_y; window.should_update_range = true; } x_source.data['x_count'] = [final_x.length]; x_source.data['category'] = [x_axis.value]; x_source.change.emit(); source.data['x'] = new_x; source.data['y'] = new_y; source.change.emit(); fig.x_range.factors = []; fig.x_range.factors = final_x; if (new_y && Array.isArray(new_y) && new_y.length) { // range init and end cannot have same value var range_end = Math.max.apply(Math, new_y); if (range_end == 0 || range_end == -Infinity) { range_end = 1; } range_slider.value = [0, Math.max.apply(Math, final_y)]; range_slider.end = range_end; fig.y_range.end = Math.max.apply(Math, final_y); fig.change.emit(); } """) sort_order.js_on_change('active', sort_order_callback) # Define layout inputs = column(*controls, width=320, height=1000) inputs.sizing_mode = "fixed" l = layout([ [desc], [inputs, p], ], sizing_mode="scale_both") return l
p.patches('x', 'y', source=source, fill_alpha=0.5, fill_color='#8E0D3F', line_color="white", line_width=0.5) year_slider = Slider(start=2012, end=2017, value=2012, step=1, title="Year") callback = CustomJS(args=dict(source=source, year=year_slider, master=income_master_list), code=""" var data = source.data; var yr = year.value; const x = data['x']; const y = data['y']; const names = data['name']; data['income'] = master[yr - 2012]; source.change.emit() """) year_slider.js_on_change('value', callback) layout = row( p, column(year_slider) ) hover = HoverTool() hover.point_policy = "follow_mouse" hover.tooltips = """ <div> <h3>@name County</h3> <div><strong>Median Income: </strong>@income</div> <div><strong>(Long, Lat): </strong>($x, $y)</div> <div></div> </div>
from bokeh.io import show from bokeh.models import Slider, CustomJS from bokeh.layouts import row from vtk_js import VtkJs vtkjs = VtkJs(resolution=10, width=400, height=400) slider = Slider(value=10, start=1, end=100, step=1) cb = CustomJS(args={'vtkjs': vtkjs}, code=""" vtkjs.resolution = cb_obj.value """) slider.js_on_change('value', cb) show(row([vtkjs, slider]))
def plot(y: list, x: list = None, label: list = None, width: int = 400, height: int = 400, gain: float = 0.4, margin_x: int = 1, title: str = "graph", script_name: str = "", slider_partitions: int = None, multiple_axes=False): """Plots that represent data with sound and can be checked interactively You can interactively check the data in graph form by moving the mouse cursor. When you enter or leave the graph image, you will be notified by voice. Also, when you move the mouse left or right on the graph image, the y-axis value corresponding to that location will be expressed with a high or low tone. A single click will read out the value corresponding to that location. Also, double-clicking switches the group according to the label specified as an option. Parameters ---------- y : list A list of values to be graphed. x : list A list of x-axis values corresponding to y-axis values. If not specified, it is substituted by the value of the equal interval. Optional. label : list A list of grouping numbers for each value, which must start with zero. You can compare the graph data by sound, switching between each number. Optional. width : int Width of the graph image (in pixels). Optional. height : int Height of the graph image (in pixels). Optional. title: str Graph name to be read out. Optional. multiple_axes: bool Set to True if you want each label to have a separate y-axis. Optional. Examples -------- >>> plot([0, 1, 2]) <IPython.core.display.HTML object> >>> plot(x=[0, 1, 2], y=[4, 5, 6], label=[0, 0, 1]) <IPython.core.display.HTML object> """ if type(y) == np.ndarray: y = y.tolist() if type(x) == np.ndarray: x = x.tolist() elif x == None: x = np.arange(len(y)).tolist() if type(label) == np.ndarray: label = label.astype(int).tolist() elif label == None: label = np.zeros_like(y).astype(int).tolist() if label: assert max(label) < len(__COLORS), "max label must be lower {}".format( len(__COLORS)) assert max(label) + 1 == len(set( label)), "label should be in {} because max label is {}.".format( list(range(max(label) + 1)), max(label)) if script_name == "": __set_context() output_notebook() plot = figure(plot_width=width, plot_height=height, tools="", toolbar_location=None) colors = [__COLORS[c] for c in label] if multiple_axes: assert max(label) == 1, "The number of labels must be two kinds" multi_axes_str = "true" y_ranges = {} for l in range(max(label) + 1): __x = np.array(x)[np.array(label) == l].tolist() __y = np.array(y)[np.array(label) == l].tolist() __c = np.array(colors)[np.array(label) == l].tolist() plot.scatter(__x, __y, line_color=__c, fill_color=__c, y_range_name=str(l)) if l == 1: plot.add_layout(LinearAxis(y_range_name=str(l)), 'right') y_ranges[str(l)] = Range1d(start=min(__y) - 1, end=max(__y) + 1) plot.extra_y_ranges = y_ranges else: multi_axes_str = "false" plot.scatter(x, y, line_color=colors, fill_color=colors) sound_js = """ const multiAxes = %s; %s if(diff[nearestIdx] > marginX) { return; } const gain = %s; // max: 1.0 osc.type = 'triangle'; // sine, square, sawtooth, triangle osc.frequency.value = 261.626 + (nearestY - minY) / (maxY - minY) * 261.626 // Hz audioGain.gain.linearRampToValueAtTime(gain, audioContext.currentTime + 0.2); // atack audioGain.gain.setTargetAtTime(0, audioContext.currentTime + 0.2, 0.5); // decay, sustain let pan = (nearestX - minX) / (maxX - minX) * 2 - 1; panNode.pan.value = pan; // left:-1 ~ right:1 """ % (multi_axes_str, __FIND_NEAREST_JS, gain) # Mouse hover on plot hover_code = """ let marginX = %s; let position = cb_data.geometry.x; %s """ % (margin_x, sound_js) callback = CustomJS(args={"x": x, "y": y, "label": label}, code=hover_code) plot.add_tools(HoverTool(tooltips=None, callback=callback)) # Single tap on plot tap_code = """ let position = cb_obj.x; const multiAxes = %s; %s %s """ % (multi_axes_str, __FIND_NEAREST_JS, __speak_js("`X is ${nearestX}. Y is ${nearestY}`")) plot.js_on_event( events.Tap, CustomJS(args={ "x": x, "y": y, "label": label }, code=tap_code)) if len(set(label)) > 1: # Double tap on plot double_tap_code = """ oscTarget = (oscTarget + 1) %% (maxLabel + 1); %s """ % (__speak_js("`label ${oscTarget} is selected`")) plot.js_on_event( events.DoubleTap, CustomJS(args={"maxLabel": max(label)}, code=double_tap_code)) # Enter or leave on plot read_label = (max(label) > 0) plot.js_on_event(events.MouseEnter, __speak_inout(title, True, read_label)) plot.js_on_event(events.MouseLeave, __speak_inout(title, False, read_label)) # slider for keyboard interaction sliders = [] for l in range(max(label) + 1): __x = np.array(x)[np.array(label) == l].tolist() if slider_partitions is None: slider_partitions = np.min([len(__x) - 1, 30]) if slider_partitions == 30: print( "The number of slider partitions has been reduced to 30 as the default limit. Please set slider_partitions as an argument if necessary." ) slider_start = np.min(__x) slider_end = np.max(__x) if slider_start == slider_end: slider_end += 1 slider_step = (slider_end - slider_start) / slider_partitions slider_code = """ oscTarget = target; let marginX = %s; let position = slider.value; %s setTimeout(function(){%s}, 3000); """ % (slider_step, sound_js, __speak_js("`X is ${nearestX}. Y is ${nearestY}`")) slider = Slider(start=slider_start, end=slider_end, value=slider_start, step=slider_step, title="label {}".format(l)) slider.js_on_change( 'value', CustomJS(args={ "x": x, "y": y, "label": label, "slider": slider, "target": l }, code=slider_code)) sliders.append(slider) # layout message1 = Div(text="""<h2>output of audio plot lib</h2>""") message2 = Div( text= """<p>There is a graph and a series of sliders to check the values. If you have a mouse, you can check the values by hovering over the graph. If you are using only a keyboard, you can move the slider to move the horizontal axis of the graph to check the value of the graph as a pitch according to the location.</p>""" ) show(column(message1, message2, row(plot, column(sliders)))) if script_name != "": from bs4 import BeautifulSoup HTML = """ <button id="unmuteButton">Push here to unmute graph</button> <script> document.getElementById('unmuteButton').addEventListener('click', function() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); audioGain = audioContext.createGain(); panNode = audioContext.createStereoPanner(); osc = audioContext.createOscillator(); osc.connect(panNode); panNode.connect(audioGain); audioGain.connect(audioContext.destination); osc.start(audioContext.currentTime); audioGain.gain.setValueAtTime(0, audioContext.currentTime); oscTarget = 0; }) </script> """ html_filename = script_name.replace(".py", ".html") soup = BeautifulSoup(open(html_filename), 'html.parser') soup.body.insert(0, BeautifulSoup(HTML, "html.parser")) # after body with open(html_filename, "w") as file: file.write(str(soup))
def multi_channel_tile_slider(dataset: TileFeaturesDataset): """ View interactively with bokeh the 3 image tiles """ n = 100 a_img_paths, b_img_paths, c_img_paths = _save_tile_images_to_local_path( dataset, n) # the plotting code plots = [] sources = [] pathes = [a_img_paths, b_img_paths, c_img_paths] plot_num = 3 for i in range(plot_num): p = figure(height=300, width=300) img_paths = pathes[i] # print(img_paths) source = ColumnDataSource(data=dict(url=[img_paths[0]] * n, url_orig=img_paths, x=[1] * n, y=[1] * n, w=[1] * n, h=[1] * n)) image = ImageURL(url="url", x="x", y="y", w="w", h="h", anchor="bottom_left") p.add_glyph(source, glyph=image) _disable_all_for_pictures(p) plots.append(p) sources.append(source) update_source_str = """ var data = source{i}.data; url = data['url'] url_orig = data['url_orig'] for (i = 0; i < url_orig.length; i++) { url[i] = url_orig[f-1] } source{i}.change.emit(); """ # the callback callback = CustomJS(args=dict(source0=sources[0], source1=sources[1], source2=sources[2]), code=f""" var f = cb_obj.value; console.log(f) {"".join([update_source_str.replace('{i}', str(i)) for i in range(plot_num)])} """) slider = Slider(start=1, end=n, value=1, step=1, title="example number") slider.js_on_change('value', callback) column_layout = [slider] curr_row = [] for i in range(len(plots)): if i != 0 and i % 3 == 0: print(curr_row) column_layout.append(row(*curr_row.copy())) curr_row = [] else: curr_row.append(plots[i]) if len(curr_row) != 0: column_layout.append(row(*curr_row.copy())) layout = column(*column_layout) show(layout)
def mru_fit_iterative(): # mostra o gráfico no notebook output_notebook() # definindo os dados X_dados = np.asarray([0.000, 0.905, 1.833, 2.770, 3.684]) Y_dados = np.asarray([0.00, 15.00, 30.00, 45.00, 60.00]) # dados a serem mudados iterativamente t = X_dados s = t r2 = coef_determinacao(Y_dados, s) dummy_texts = ['R2 = {}'.format(r2), '', '', '', ''] dummy_x = [0.1 for _ in X_dados] dummy_y = [35 for _ in X_dados] # cores a serem usadas no plot COLOR_DADOS = "#0095DD" COLOR_AJUSTE = "#E34A33" # cria display dinâmico de valores TOOLTIPS_POSICAO = [("Posição [cm]", "@y{0.00}"), ("Tempo [s]", "@x")] # cria a fonte dinâmica para representar o ajuste source_posicao = ColumnDataSource(data=dict( x=t, y=s, dados=Y_dados, text=dummy_texts, posx=dummy_x, posy=dummy_y)) # cria a figura onde os dados e o ajuste serão mostrados plot_posicao = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS_POSICAO, title='Ajuste da reta S(t) = a + bt.') # plotando os dados plot_posicao.line(X_dados, Y_dados, color=COLOR_DADOS, legend='Dados', line_width=3) plot_posicao.scatter(X_dados, Y_dados, color=COLOR_DADOS, legend='Dados', fill_color='#FFFFFF', size=18) # plotando a reta de ajuste (dinâmica) plot_posicao.line('x', 'y', source=source_posicao, color=COLOR_AJUSTE, legend='Ajuste', line_width=3, line_dash="5 5") plot_posicao.scatter('x', 'y', source=source_posicao, color=COLOR_AJUSTE, legend='Ajuste', marker='x', size=16) # definindo os labels do axis plot_posicao.xaxis[0].axis_label = 'Tempo, t [s]' plot_posicao.yaxis[0].axis_label = 'Posição, S(t) [cm]' # mudando as fontes dos labels FONT_SIZE = '16px' plot_posicao.xaxis.axis_label_text_font_size = FONT_SIZE plot_posicao.xaxis.major_label_text_font_size = FONT_SIZE plot_posicao.yaxis.axis_label_text_font_size = FONT_SIZE plot_posicao.yaxis.major_label_text_font_size = FONT_SIZE plot_posicao.xaxis.axis_label_text_font_style = 'normal' plot_posicao.yaxis.axis_label_text_font_style = 'normal' # remove a barra lateral com o símbolo do bokeh plot_posicao.toolbar.logo = None plot_posicao.toolbar_location = None # mudando a legenda de lugar plot_posicao.legend.location = 'top_left' # criando o label citation = LabelSet(x=1.5, y=55, text='text', source=source_posicao, render_mode='css', border_line_color='black', background_fill_color='white') plot_posicao.add_layout(citation) # criando os sliders slider_posicao_inicial = Slider( start=-10, end=10, value=0, step=-0.01, bar_color=COLOR_AJUSTE, title="Coeficiente linear, a" ) #orientation='vertical', direction='rtl') slider_velocidade = Slider(start=-20, end=20, value=5, step=-0.05, bar_color=COLOR_AJUSTE, title="Coeficiente angular, b" ) #orientation='vertical', direction='rtl') # cirando o callback para mudar a reta de ajuste dinamicamente callback_ajuste = CustomJS(args=dict( source=source_posicao, velocidade=slider_velocidade, posicao_inicial=slider_posicao_inicial), code=""" const data = source.data; const v = velocidade.value; const s0 = posicao_inicial.value; const t = data['x']; const s = data['y']; const yDado = data['dados']; const text = data['text']; for (let i = 0; i < t.length; i++) { s[i] = s0 + v*t[i]; } let mean_value = (arr) => { let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i]; } let mean = sum/arr.length; return mean; } let r2 = (y_dado, y_ajuste) => { let s_res = 0; let s_tot = 0; let mean_dado = mean_value(y_dado); for (let i = 0; i < y_dado.length; i++) { s_res += (y_dado[i] - y_ajuste[i])**2; s_tot += (y_dado[i] - mean_dado)**2; } let score = Math.round( (1 - s_res/s_tot)*1000 ) / 1000; if (score >= 0) { return score; } else { return 'nan'; } } text[0] = `R2 = ${r2(yDado, s).toString()}`; source.change.emit(); """) # criando os sliders slider_posicao_inicial.js_on_change('value', callback_ajuste) slider_velocidade.js_on_change('value', callback_ajuste) # citation.js_on_change('value', callback_ajuste) # criando o layout de como ficará disposto o gráfico e os sliders layout = row([ plot_posicao, column([ slider_posicao_inicial, slider_velocidade, ], sizing_mode='scale_width'), ], sizing_mode='scale_width') # mostrando o plot show(layout)
return v | 0 } const color = source.data['color'] const text_color = source.data['text_color'] const R = toInt(red.value) const G = toInt(green.value) const B = toInt(blue.value) color[0] = rgbToHex(R, G, B) text_color[0] = '#ffffff' if ((R > 127) || (G > 127) || (B > 127)) { text_color[0] = '#000000' } source.change.emit() """) red_slider.js_on_change('value', callback) blue_slider.js_on_change('value', callback) green_slider.js_on_change('value', callback) # plot 2: create a color spectrum with a hover-over tool to inspect hex codes brightness = 0.8 # change to have brighter/darker colors crx = list(range(1, 1001)) # the resolution is 1000 colors cry = [5 for i in range(len(crx))] crcolor, crRGBs = generate_color_range(1000, brightness) # produce spectrum # make data source object to allow information to be displayed by hover tool crsource = ColumnDataSource( data=dict(x=crx, y=cry, crcolor=crcolor, RGBs=crRGBs)) # create second plot
# source = ColumnDataSource(data=data) kguess = (np.max(yobs)-np.min(yobs))/(np.max(xobs)-np.min(xobs)) *5. k_slider = Slider(start=-1, end=0, value=1, step=0.001, title="k") b_slider = Slider(start=-1000, end=1000, value=1, step=0.001, title="b") callback = CustomJS(args=dict(lineColumn=lineColumn, kval=k_slider, bval=b_slider), code=""" const data = lineColumn.data ; const k = kval.value ; const b = bval.value ; const x = data['xline'] const y = data['yline'] for (var i = 0; i < x.length; i++) { y[i] = b + k*x[i] ; } lineColumn.change.emit(); """) k_slider.js_on_change('value', callback) b_slider.js_on_change('value', callback) layout = row( ax1, column(k_slider, b_slider), ) output_file("2.html", title="der") show(layout)
} source.change.emit(); """) callback_ion = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.range var x = data['x'] var y = data['y'] var pow = (Math.log(y[100])/Math.log(x[100])) console.log(pow) var delta = (f[1] - f[0])/x.length for (var i = 0; i < x.length; i++) { x[i] = delta*i + f[0] y[i] = Math.pow(x[i], pow) } source.change.emit(); """) slider = Slider(start=0, end=5, step=0.1, value=1, title="Bokeh Slider - Power") slider.js_on_change('value', callback_single) ion_range_slider = IonRangeSlider(start=0.01, end=0.99, step=0.01, range=(min(x), max(x)), title='Ion Range Slider - Range') ion_range_slider.js_on_change('range', callback_ion) layout = column(plot, slider, ion_range_slider) show(layout)
def photometry_plot(obj_id, user, width=600, height=300, device="browser"): """Create object photometry scatter plot. Parameters ---------- obj_id : str ID of Obj to be plotted. Returns ------- dict Returns Bokeh JSON embedding for the desired plot. """ data = pd.read_sql( DBSession().query( Photometry, Telescope.nickname.label("telescope"), Instrument.name.label("instrument"), ).join(Instrument, Instrument.id == Photometry.instrument_id).join( Telescope, Telescope.id == Instrument.telescope_id).filter( Photometry.obj_id == obj_id).filter( Photometry.groups.any( Group.id.in_([g.id for g in user.accessible_groups ]))).statement, DBSession().bind, ) if data.empty: return None, None, None # get spectra to annotate on phot plots spectra = (Spectrum.query_records_accessible_by(user).filter( Spectrum.obj_id == obj_id).all()) data['color'] = [get_color(f) for f in data['filter']] labels = [] for i, datarow in data.iterrows(): label = f'{datarow["instrument"]}/{datarow["filter"]}' if datarow['origin'] is not None: label += f'/{datarow["origin"]}' labels.append(label) data['label'] = labels data['zp'] = PHOT_ZP data['magsys'] = 'ab' data['alpha'] = 1.0 data['lim_mag'] = ( -2.5 * np.log10(data['fluxerr'] * PHOT_DETECTION_THRESHOLD) + data['zp']) # Passing a dictionary to a bokeh datasource causes the frontend to die, # deleting the dictionary column fixes that del data['original_user_data'] # keep track of things that are only upper limits data['hasflux'] = ~data['flux'].isna() # calculate the magnitudes - a photometry point is considered "significant" # or "detected" (and thus can be represented by a magnitude) if its snr # is above PHOT_DETECTION_THRESHOLD obsind = data['hasflux'] & (data['flux'].fillna(0.0) / data['fluxerr'] >= PHOT_DETECTION_THRESHOLD) data.loc[~obsind, 'mag'] = None data.loc[obsind, 'mag'] = -2.5 * np.log10(data[obsind]['flux']) + PHOT_ZP # calculate the magnitude errors using standard error propagation formulae # https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulae data.loc[~obsind, 'magerr'] = None coeff = 2.5 / np.log(10) magerrs = np.abs(coeff * data[obsind]['fluxerr'] / data[obsind]['flux']) data.loc[obsind, 'magerr'] = magerrs data['obs'] = obsind data['stacked'] = False split = data.groupby('label', sort=False) finite = np.isfinite(data['flux']) fdata = data[finite] lower = np.min(fdata['flux']) * 0.95 upper = np.max(fdata['flux']) * 1.05 active_drag = None if "mobile" in device or "tablet" in device else "box_zoom" tools = ('box_zoom,pan,reset' if "mobile" in device or "tablet" in device else "box_zoom,wheel_zoom,pan,reset,save") plot = figure( aspect_ratio=2.0 if device == "mobile_landscape" else 1.5, sizing_mode='scale_both', active_drag=active_drag, tools=tools, toolbar_location='above', toolbar_sticky=True, y_range=(lower, upper), min_border_right=16, ) imhover = HoverTool(tooltips=tooltip_format) imhover.renderers = [] plot.add_tools(imhover) model_dict = {} for i, (label, sdf) in enumerate(split): # for the flux plot, we only show things that have a flux value df = sdf[sdf['hasflux']] key = f'obs{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker='circle', fill_color='color', alpha='alpha', source=ColumnDataSource(df), ) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker='circle', fill_color='color', source=ColumnDataSource(data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], stacked=[], instrument=[], )), ) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df.iterrows(): px = ro['mjd'] py = ro['flux'] err = ro['fluxerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource(data=dict(xs=y_err_x, ys=y_err_y, color=df['color'], alpha=[1.0] * len(df))), ) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', source=ColumnDataSource(data=dict(xs=[], ys=[], color=[])), ) plot.xaxis.axis_label = 'MJD' if device == "mobile_portrait": plot.xaxis.ticker.desired_num_ticks = 5 plot.yaxis.axis_label = 'Flux (μJy)' plot.toolbar.logo = None colors_labels = data[['color', 'label']].drop_duplicates() toggle = CheckboxWithLegendGroup( labels=colors_labels.label.tolist(), active=list(range(len(colors_labels))), colors=colors_labels.color.tolist(), width=width // 5, inline=True if "tablet" in device else False, ) # TODO replace `eval` with Namespaces # https://github.com/bokeh/bokeh/pull/6340 toggle.js_on_click( CustomJS( args={ 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'togglef.js')).read(), )) slider = Slider( start=0.0, end=15.0, value=0.0, step=1.0, title='Binsize (days)', max_width=350, margin=(4, 10, 0, 10), ) callback = CustomJS( args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackf.js')).read().replace( 'default_zp', str(PHOT_ZP)).replace( 'detect_thresh', str(PHOT_DETECTION_THRESHOLD)), ) slider.js_on_change('value', callback) # Mark the first and last detections detection_dates = data[data['hasflux']]['mjd'] if len(detection_dates) > 0: first = round(detection_dates.min(), 6) last = round(detection_dates.max(), 6) first_color = "#34b4eb" last_color = "#8992f5" midpoint = (upper + lower) / 2 line_top = 5 * upper - 4 * midpoint line_bottom = 5 * lower - 4 * midpoint y = np.linspace(line_bottom, line_top, num=5000) first_r = plot.line( x=np.full(5000, first), y=y, line_alpha=0.5, line_color=first_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("First detection", f'{first}')], renderers=[first_r], )) last_r = plot.line( x=np.full(5000, last), y=y, line_alpha=0.5, line_color=last_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("Last detection", f'{last}')], renderers=[last_r], )) # Mark when spectra were taken annotate_spec(plot, spectra, lower, upper) plot_layout = (column(plot, toggle) if "mobile" in device or "tablet" in device else row(plot, toggle)) layout = column(slider, plot_layout, sizing_mode='scale_width', width=width) p1 = Panel(child=layout, title='Flux') # now make the mag light curve ymax = (np.nanmax(( np.nanmax(data.loc[obsind, 'mag']) if any(obsind) else np.nan, np.nanmax(data.loc[~obsind, 'lim_mag']) if any(~obsind) else np.nan, )) + 0.1) ymin = (np.nanmin(( np.nanmin(data.loc[obsind, 'mag']) if any(obsind) else np.nan, np.nanmin(data.loc[~obsind, 'lim_mag']) if any(~obsind) else np.nan, )) - 0.1) xmin = data['mjd'].min() - 2 xmax = data['mjd'].max() + 2 plot = figure( aspect_ratio=2.0 if device == "mobile_landscape" else 1.5, sizing_mode='scale_both', width=width, active_drag=active_drag, tools=tools, y_range=(ymax, ymin), x_range=(xmin, xmax), toolbar_location='above', toolbar_sticky=True, x_axis_location='above', ) # Mark the first and last detections again detection_dates = data[obsind]['mjd'] if len(detection_dates) > 0: first = round(detection_dates.min(), 6) last = round(detection_dates.max(), 6) midpoint = (ymax + ymin) / 2 line_top = 5 * ymax - 4 * midpoint line_bottom = 5 * ymin - 4 * midpoint y = np.linspace(line_bottom, line_top, num=5000) first_r = plot.line( x=np.full(5000, first), y=y, line_alpha=0.5, line_color=first_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("First detection", f'{first}')], renderers=[first_r], )) last_r = plot.line( x=np.full(5000, last), y=y, line_alpha=0.5, line_color=last_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("Last detection", f'{last}')], renderers=[last_r], point_policy='follow_mouse', )) # Mark when spectra were taken annotate_spec(plot, spectra, ymax, ymin) imhover = HoverTool(tooltips=tooltip_format) imhover.renderers = [] plot.add_tools(imhover) model_dict = {} for i, (label, df) in enumerate(split): key = f'obs{i}' model_dict[key] = plot.scatter( x='mjd', y='mag', color='color', marker='circle', fill_color='color', alpha='alpha', source=ColumnDataSource(df[df['obs']]), ) imhover.renderers.append(model_dict[key]) unobs_source = df[~df['obs']].copy() unobs_source.loc[:, 'alpha'] = 0.8 key = f'unobs{i}' model_dict[key] = plot.scatter( x='mjd', y='lim_mag', color='color', marker='inverted_triangle', fill_color='white', line_color='color', alpha='alpha', source=ColumnDataSource(unobs_source), ) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='mag', color='color', marker='circle', fill_color='color', source=ColumnDataSource(data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[], )), ) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df[df['obs']].iterrows(): px = ro['mjd'] py = ro['mag'] err = ro['magerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource(data=dict( xs=y_err_x, ys=y_err_y, color=df[df['obs']]['color'], alpha=[1.0] * len(df[df['obs']]), )), ) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', source=ColumnDataSource(data=dict(xs=[], ys=[], color=[])), ) key = f'unobsbin{i}' model_dict[key] = plot.scatter( x='mjd', y='lim_mag', color='color', marker='inverted_triangle', fill_color='white', line_color='color', alpha=0.8, source=ColumnDataSource(data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[], )), ) imhover.renderers.append(model_dict[key]) key = f'all{i}' model_dict[key] = ColumnDataSource(df) key = f'bold{i}' model_dict[key] = ColumnDataSource(df[[ 'mjd', 'flux', 'fluxerr', 'mag', 'magerr', 'filter', 'zp', 'magsys', 'lim_mag', 'stacked', ]]) plot.xaxis.axis_label = 'MJD' plot.yaxis.axis_label = 'AB mag' plot.toolbar.logo = None obj = DBSession().query(Obj).get(obj_id) if obj.dm is not None: plot.extra_y_ranges = { "Absolute Mag": Range1d(start=ymax - obj.dm, end=ymin - obj.dm) } plot.add_layout( LinearAxis(y_range_name="Absolute Mag", axis_label="m - DM"), 'right') now = Time.now().mjd plot.extra_x_ranges = { "Days Ago": Range1d(start=now - xmin, end=now - xmax) } plot.add_layout(LinearAxis(x_range_name="Days Ago", axis_label="Days Ago"), 'below') colors_labels = data[['color', 'label']].drop_duplicates() toggle = CheckboxWithLegendGroup( labels=colors_labels.label.tolist(), active=list(range(len(colors_labels))), colors=colors_labels.color.tolist(), width=width // 5, inline=True if "tablet" in device else False, ) # TODO replace `eval` with Namespaces # https://github.com/bokeh/bokeh/pull/6340 toggle.js_on_click( CustomJS( args={ 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'togglem.js')).read(), )) slider = Slider( start=0.0, end=15.0, value=0.0, step=1.0, title='Binsize (days)', max_width=350, margin=(4, 10, 0, 10), ) button = Button(label="Export Bold Light Curve to CSV") button.js_on_click( CustomJS( args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', "download.js")).read().replace('objname', obj_id).replace( 'default_zp', str(PHOT_ZP)), )) # Don't need to expose CSV download on mobile top_layout = (slider if "mobile" in device or "tablet" in device else row( slider, button)) callback = CustomJS( args={ 'slider': slider, 'toggle': toggle, **model_dict }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackm.js')).read().replace( 'default_zp', str(PHOT_ZP)).replace( 'detect_thresh', str(PHOT_DETECTION_THRESHOLD)), ) slider.js_on_change('value', callback) plot_layout = (column(plot, toggle) if "mobile" in device or "tablet" in device else row(plot, toggle)) layout = column(top_layout, plot_layout, sizing_mode='scale_width', width=width) p2 = Panel(child=layout, title='Mag') # now make period plot # get periods from annotations annotation_list = obj.get_annotations_readable_by(user) period_labels = [] period_list = [] for an in annotation_list: if 'period' in an.data: period_list.append(an.data['period']) period_labels.append(an.origin + ": %.9f" % an.data['period']) if len(period_list) > 0: period = period_list[0] else: period = None # don't generate if no period annotated if period is not None: # bokeh figure for period plotting period_plot = figure( aspect_ratio=1.5, sizing_mode='scale_both', active_drag='box_zoom', tools='box_zoom,wheel_zoom,pan,reset,save', y_range=(ymax, ymin), x_range=(-0.1, 1.1), # initially one phase toolbar_location='above', toolbar_sticky=False, x_axis_location='below', ) # axis labels period_plot.xaxis.axis_label = 'phase' period_plot.yaxis.axis_label = 'mag' period_plot.toolbar.logo = None # do we have a distance modulus (dm)? obj = DBSession().query(Obj).get(obj_id) if obj.dm is not None: period_plot.extra_y_ranges = { "Absolute Mag": Range1d(start=ymax - obj.dm, end=ymin - obj.dm) } period_plot.add_layout( LinearAxis(y_range_name="Absolute Mag", axis_label="m - DM"), 'right') # initiate hover tool period_imhover = HoverTool(tooltips=tooltip_format) period_imhover.renderers = [] period_plot.add_tools(period_imhover) # initiate period radio buttons period_selection = RadioGroup(labels=period_labels, active=0) phase_selection = RadioGroup(labels=["One phase", "Two phases"], active=0) # store all the plot data period_model_dict = {} # iterate over each filter for i, (label, df) in enumerate(split): # fold x-axis on period in days df['mjd_folda'] = (df['mjd'] % period) / period df['mjd_foldb'] = df['mjd_folda'] + 1.0 # phase plotting for ph in ['a', 'b']: key = 'fold' + ph + f'{i}' period_model_dict[key] = period_plot.scatter( x='mjd_fold' + ph, y='mag', color='color', marker='circle', fill_color='color', alpha='alpha', visible=('a' in ph), source=ColumnDataSource( df[df['obs']]), # only visible data ) # add to hover tool period_imhover.renderers.append(period_model_dict[key]) # errorbars for phases key = 'fold' + ph + f'err{i}' y_err_x = [] y_err_y = [] # get each visible error value for d, ro in df[df['obs']].iterrows(): px = ro['mjd_fold' + ph] py = ro['mag'] err = ro['magerr'] # set up error tuples y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) # plot phase errors period_model_dict[key] = period_plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', visible=('a' in ph), source=ColumnDataSource(data=dict( xs=y_err_x, ys=y_err_y, color=df[df['obs']]['color'], alpha=[1.0] * len(df[df['obs']]), )), ) # toggle for folded photometry period_toggle = CheckboxWithLegendGroup( labels=colors_labels.label.tolist(), active=list(range(len(colors_labels))), colors=colors_labels.color.tolist(), width=width // 5, ) # use javascript to perform toggling on click # TODO replace `eval` with Namespaces # https://github.com/bokeh/bokeh/pull/6340 period_toggle.js_on_click( CustomJS( args={ 'toggle': period_toggle, 'numphases': phase_selection, 'p': period_plot, **period_model_dict, }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'togglep.js')).read(), )) # set up period adjustment text box period_title = Div(text="Period (days): ") period_textinput = TextInput( value=str(period if period is not None else 0.0)) period_textinput.js_on_change( 'value', CustomJS( args={ 'textinput': period_textinput, 'toggle': period_toggle, 'numphases': phase_selection, 'p': period_plot, **period_model_dict, }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'foldphase.js')).read(), ), ) # a way to modify the period period_double_button = Button(label="*2") period_double_button.js_on_click( CustomJS( args={'textinput': period_textinput}, code=""" const period = parseFloat(textinput.value); textinput.value = parseFloat(2.*period).toFixed(9); """, )) period_halve_button = Button(label="/2") period_halve_button.js_on_click( CustomJS( args={'textinput': period_textinput}, code=""" const period = parseFloat(textinput.value); textinput.value = parseFloat(period/2.).toFixed(9); """, )) # a way to select the period period_selection.js_on_click( CustomJS( args={ 'textinput': period_textinput, 'periods': period_list }, code=""" textinput.value = parseFloat(periods[this.active]).toFixed(9); """, )) phase_selection.js_on_click( CustomJS( args={ 'textinput': period_textinput, 'toggle': period_toggle, 'numphases': phase_selection, 'p': period_plot, **period_model_dict, }, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'foldphase.js')).read(), )) # layout period_column = column( period_toggle, period_title, period_textinput, period_selection, row(period_double_button, period_halve_button, width=180), phase_selection, width=180, ) period_layout = column( row(period_plot, period_column), sizing_mode='scale_width', width=width, ) # Period panel p3 = Panel(child=period_layout, title='Period') # tabs for mag, flux, period tabs = Tabs(tabs=[p2, p1, p3], width=width, height=height, sizing_mode='fixed') else: # tabs for mag, flux tabs = Tabs(tabs=[p2, p1], width=width, height=height, sizing_mode='fixed') return bokeh_embed.json_item(tabs)
output_file("js_on_change.html") x = [x * 0.005 for x in range(0, 200)] y = x source = ColumnDataSource(data=dict(x=x, y=y)) plot = Figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6, color='red') callback = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.value var x = data['x'] var y = data['y'] for (var i = 0; i < x.length; i++) { y[i] = Math.pow(x[i], f) } source.change.emit(); """) slider = Slider(start=0.1, end=4, value=1, step=.1, title="power") slider.js_on_change('value', callback) lay = layout([slider], [plot]) content = interact_plot_grid(lay) description = 'Interact Plot'
def geoplot(gdf_in, fig=None, figsize=None, title="", xlabel="Longitude", ylabel="Latitude", color="blue", colormap=None, colormap_uselog=False, colormap_range=None, category=None, dropdown=None, slider=None, slider_range=None, slider_name="", show_colorbar=True, xrange=None, yrange=None, hovertool=True, hovertool_columns=[], simplify_shapes=None, tile_provider="CARTODBPOSITRON_RETINA", tile_provider_url=None, tile_attribution="", toolbar_location="right", show_figure=True, return_figure=True, return_html=False, legend=True, webgl=True, **kwargs): """Doc-String: TODO""" gdf = gdf_in.copy() # Check layertypes: layertypes = [] if "Point" in str(gdf.geom_type.unique()): layertypes.append("Point") if "Line" in str(gdf.geom_type.unique()): layertypes.append("Line") if "Polygon" in str(gdf.geom_type.unique()): layertypes.append("Polygon") if len(layertypes) > 1: raise Exception( "Can only plot GeoDataFrames/Series with single type of geometry (either Point, Line or Polygon). Provided is a GeoDataFrame/Series with types: %s" % layertypes) # Get and check provided parameters for geoplot: figure_options = { "title": title, "x_axis_label": xlabel, "y_axis_label": ylabel, "plot_width": 600, "plot_height": 400, "toolbar_location": toolbar_location, "active_scroll": "wheel_zoom", } if not figsize is None: width, height = figsize figure_options["plot_width"] = width figure_options["plot_height"] = height if webgl: figure_options["output_backend"] = "webgl" if not fig is None: raise NotImplementedError("Parameter <figure> not yet implemented.") # Convert GeoDataFrame to Web Mercador Projection: gdf.to_crs({"init": "epsg:3857"}, inplace=True) # Simplify shapes if wanted: if isinstance(simplify_shapes, numbers.Number): if layertypes[0] in ["Line", "Polygon"]: gdf["geometry"] = gdf["geometry"].simplify(simplify_shapes) elif not simplify_shapes is None: raise ValueError( "<simplify_shapes> parameter only accepts numbers or None.") # Check for category, dropdown or slider (choropleth map column): category_options = 0 if not category is None: category_options += 1 category_columns = [category] if not dropdown is None: category_options += 1 category_columns = dropdown if not slider is None: category_options += 1 category_columns = slider if category_options > 1: raise ValueError( "Only one of <category>, <dropdown> or <slider> parameters is allowed to be used at once." ) # Check for category (single choropleth plot): if category is None: pass elif isinstance(category, (list, tuple)): raise ValueError( "For <category>, please provide an existing single column of the GeoDataFrame." ) elif category in gdf.columns: pass else: raise ValueError( "Could not find column '%s' in GeoDataFrame. For <category>, please provide an existing single column of the GeoDataFrame." % category) # Check for dropdown (multiple choropleth plots via dropdown selection): if dropdown is None: pass elif not isinstance(dropdown, (list, tuple)): raise ValueError( "For <dropdown>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in dropdown: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <dropdown> in GeoDataFrame. " % col) # Check for slider (multiple choropleth plots via slider selection): if slider is None: pass elif not isinstance(slider, (list, tuple)): raise ValueError( "For <slider>, please provide a list/tuple of existing columns of the GeoDataFrame." ) else: for col in slider: if col not in gdf.columns: raise ValueError( "Could not find column '%s' for <slider> in GeoDataFrame. " % col) if not slider_range is None: if not isinstance(slider_range, Iterable): raise ValueError( "<slider_range> has to be a type that is iterable like list, tuple, range, ..." ) else: slider_range = list(slider_range) if len(slider_range) != len(slider): raise ValueError( "The number of elements in <slider_range> has to be the same as in <slider>." ) steps = [] for i in range(len(slider_range) - 1): steps.append(slider_range[i + 1] - slider_range[i]) if len(set(steps)) > 1: raise ValueError( "<slider_range> has to have equal step size between each elements (like a range-object)." ) else: slider_step = steps[0] slider_start = slider_range[0] slider_end = slider_range[-1] # Check colormap if either <category>, <dropdown> or <slider> is choosen: if category_options == 1: if colormap is None: colormap = blue_colormap elif isinstance(colormap, (tuple, list)): if len(colormap) > 1: pass else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys()))) elif isinstance(colormap, str): if colormap in all_palettes: colormap = all_palettes[colormap] colormap = colormap[max(colormap.keys())] else: raise ValueError( "Could not find <colormap> with name %s. The following predefined colormaps are supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (colormap, list(all_palettes.keys()))) else: raise ValueError( "<colormap> only accepts a list/tuple of at least two colors or the name of one of the following predefined colormaps (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): %s" % (list(all_palettes.keys()))) else: if isinstance(color, str): colormap = [color] else: raise ValueError( "<color> has to be a string specifying the fill_color of the map glyph." ) # Create Figure to draw: p = figure(x_axis_type="mercator", y_axis_type="mercator", **figure_options) # Get ridd of zoom on axes: for t in p.tools: if type(t) == WheelZoomTool: t.zoom_on_axis = False # Add Tile Source as Background: p = _add_backgroundtile(p, tile_provider, tile_provider_url, tile_attribution) # Hide legend if wanted: if not legend: p.legend.visible = False elif isinstance(legend, str): pass else: legend = "GeoLayer" # Define colormapper: if len(colormap) == 1: kwargs["fill_color"] = colormap[0] elif not category is None: # Check if category column is numerical: if not issubclass(gdf[category].dtype.type, np.number): raise NotImplementedError( "<category> plot only yet implemented for numerical columns. Column '%s' is not numerical." % category) field = category colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[field].min() colormapper_options["high"] = gdf[field].max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = str(field) elif not dropdown is None: # Check if all columns in dropdown selection are numerical: for col in dropdown: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<dropdown> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col) field = dropdown[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[dropdown].min().min() colormapper_options["high"] = gdf[dropdown].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = "Geolayer" elif not slider is None: # Check if all columns in dropdown selection are numerical: for col in slider: if not issubclass(gdf[col].dtype.type, np.number): raise NotImplementedError( "<slider> plot only yet implemented for numerical columns. Column '%s' is not numerical." % col) field = slider[0] colormapper_options = {"palette": colormap} if not colormap_range is None: if not isinstance(colormap_range, (tuple, list)): raise ValueError( "<colormap_range> can only be 'None' or a tuple/list of form (min, max)." ) elif len(colormap_range) == 2: colormapper_options["low"] = colormap_range[0] colormapper_options["high"] = colormap_range[1] else: colormapper_options["low"] = gdf[slider].min().min() colormapper_options["high"] = gdf[slider].max().max() if colormap_uselog: colormapper = LogColorMapper(**colormapper_options) else: colormapper = LinearColorMapper(**colormapper_options) kwargs["fill_color"] = {"field": "Colormap", "transform": colormapper} if not isinstance(legend, str): legend = "Geolayer" # Check for Hovertool columns: if hovertool: if not isinstance(hovertool_columns, (list, tuple)): if hovertool_columns == "all": hovertool_columns = list( filter(lambda col: col != "geometry", df_shapes.columns)) else: raise ValueError( "<hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." ) elif len(hovertool_columns) == 0: if not category is None: hovertool_columns = [category] elif not dropdown is None: hovertool_columns = dropdown elif not slider is None: hovertool_columns = slider else: hovertool_columns = [] else: for col in hovertool_columns: if col not in gdf.columns: raise ValueError( "Could not find columns '%s' in GeoDataFrame. <hovertool_columns> has to be a list of columns of the GeoDataFrame or the string 'all'." % col) else: if category is None: hovertool_columns = [] else: hovertool_columns = [category] # Reduce DataFrame to needed columns: additional_columns = [] for kwarg, value in kwargs.items(): if isinstance(value, Hashable): if value in gdf.columns: additional_columns.append(value) if category_options == 0: gdf = gdf[list(set(hovertool_columns) | set(additional_columns)) + ["geometry"]] else: gdf = gdf[list( set(hovertool_columns) | set(category_columns) | set(additional_columns)) + ["geometry"]] gdf["Colormap"] = gdf[field] field = "Colormap" # Create GeoJSON DataSource for Plot: geo_source = GeoJSONDataSource(geojson=gdf.to_json()) # Draw Glyph on Figure: layout = None if "Point" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] p.scatter(x="x", y="y", source=geo_source, legend=legend, **kwargs) if "Line" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = kwargs["fill_color"] del kwargs["fill_color"] p.multi_line(xs="xs", ys="ys", source=geo_source, legend=legend, **kwargs) if "Polygon" in layertypes: if "line_color" not in kwargs: kwargs["line_color"] = "black" # Plot polygons: p.patches(xs="xs", ys="ys", source=geo_source, legend=legend, **kwargs) if hovertool and (category_options == 1 or len(hovertool_columns) > 0): my_hover = HoverTool() my_hover.tooltips = [(str(col), "@{%s}" % col) for col in hovertool_columns] p.add_tools(my_hover) if show_colorbar and category_options == 1: colorbar_options = { "color_mapper": colormapper, "label_standoff": 12, "border_line_color": None, "location": (0, 0), } if colormap_uselog: colorbar_options["ticker"] = LogTicker() colorbar = ColorBar(**colorbar_options) p.add_layout(colorbar, "right") if not dropdown is None: # Define Dropdown widget: dropdown_widget = Dropdown(label="Select Choropleth Layer", menu=list(zip(dropdown, dropdown))) # Define Callback for Dropdown widget: callback = CustomJS( args=dict(dropdown_widget=dropdown_widget, geo_source=geo_source, p=p), code=""" //Change selection of field for Colormapper for choropleth plot: geo_source.data["Colormap"] = geo_source.data[dropdown_widget.value]; geo_source.change.emit(); //p.legend[0].items[0]["label"] = dropdown_widget.value; """, ) dropdown_widget.js_on_change("value", callback) # Add Dropdown widget above the plot: layout = column(dropdown_widget, p) if not slider is None: if slider_range is None: slider_start = 0 slider_end = len(slider) - 1 slider_step = 1 value2name = ColumnDataSource({ "Values": np.arange(slider_start, slider_end + slider_step, slider_step), "Names": slider, }) # Define Slider widget: slider_widget = Slider( start=slider_start, end=slider_end, value=slider_start, step=slider_step, title=slider_name, ) # Define Callback for Slider widget: callback = CustomJS( args=dict( slider_widget=slider_widget, geo_source=geo_source, value2name=value2name, ), code=""" //Change selection of field for Colormapper for choropleth plot: var slider_value = slider_widget.value; for(i=0; i<value2name.data["Names"].length; i++) { if (value2name.data["Values"][i] == slider_value) { var name = value2name.data["Names"][i]; } } geo_source.data["Colormap"] = geo_source.data[name]; geo_source.change.emit(); """, ) slider_widget.js_on_change("value", callback) # Add Slider widget above the plot: layout = column(slider_widget, p) # Set click policy for legend: p.legend.click_policy = "hide" # Display plot and if wanted return plot: if layout is None: layout = p # Display plot if wanted if show_figure: show(layout) # Return as (embeddable) HTML if wanted: if return_html: return embedded_html(layout) # Return plot: if return_figure: return layout
return v | 0 } const color = source.data['color'] const text_color = source.data['text_color'] const R = toInt(red.value) const G = toInt(green.value) const B = toInt(blue.value) color[0] = rgbToHex(R, G, B) text_color[0] = '#ffffff' if ((R > 127) || (G > 127) || (B > 127)) { text_color[0] = '#000000' } source.change.emit() """) red_slider.js_on_change('value', callback) blue_slider.js_on_change('value', callback) green_slider.js_on_change('value', callback) # plot 2: create a color spectrum with a hover-over tool to inspect hex codes brightness = 0.8 # change to have brighter/darker colors crx = list(range(1,1001)) # the resolution is 1000 colors cry = [ 5 for i in range(len(crx)) ] crcolor, crRGBs = generate_color_range(1000,brightness) # produce spectrum # make data source object to allow information to be displayed by hover tool crsource = ColumnDataSource(data=dict(x=crx, y=cry, crcolor=crcolor, RGBs=crRGBs)) # create second plot p2 = figure(x_range=(0,1000), y_range=(0,10),
def spectroscopy_plot(obj_id, user, spec_id=None, width=600, device="browser"): obj = Obj.query.get(obj_id) spectra = ( DBSession() .query(Spectrum) .join(Obj) .join(GroupSpectrum) .filter( Spectrum.obj_id == obj_id, GroupSpectrum.group_id.in_([g.id for g in user.accessible_groups]), ) ).all() if spec_id is not None: spectra = [spec for spec in spectra if spec.id == int(spec_id)] if len(spectra) == 0: return None, None, None rainbow = cm.get_cmap('rainbow', len(spectra)) palette = list(map(rgb2hex, rainbow(range(len(spectra))))) color_map = dict(zip([s.id for s in spectra], palette)) data = [] for i, s in enumerate(spectra): # normalize spectra to a median flux of 1 for easy comparison normfac = np.nanmedian(np.abs(s.fluxes)) normfac = normfac if normfac != 0.0 else 1e-20 df = pd.DataFrame( { 'wavelength': s.wavelengths, 'flux': s.fluxes / normfac, 'id': s.id, 'telescope': s.instrument.telescope.name, 'instrument': s.instrument.name, 'date_observed': s.observed_at.isoformat(sep=' ', timespec='seconds'), 'pi': ( s.assignment.run.pi if s.assignment is not None else ( s.followup_request.allocation.pi if s.followup_request is not None else "" ) ), } ) data.append(df) data = pd.concat(data) data.sort_values(by=['date_observed', 'wavelength'], inplace=True) dfs = [] for i, s in enumerate(spectra): # Smooth the spectrum by using a rolling average df = ( pd.DataFrame({'wavelength': s.wavelengths, 'flux': s.fluxes}) .rolling(2) .mean(numeric_only=True) .dropna() ) dfs.append(df) smoothed_data = pd.concat(dfs) split = data.groupby('id', sort=False) hover = HoverTool( tooltips=[ ('wavelength', '@wavelength{0,0.000}'), ('flux', '@flux'), ('telesecope', '@telescope'), ('instrument', '@instrument'), ('UTC date observed', '@date_observed'), ('PI', '@pi'), ] ) smoothed_max = np.max(smoothed_data['flux']) smoothed_min = np.min(smoothed_data['flux']) ymax = smoothed_max * 1.05 ymin = smoothed_min - 0.05 * (smoothed_max - smoothed_min) xmin = np.min(data['wavelength']) - 100 xmax = np.max(data['wavelength']) + 100 if obj.redshift is not None and obj.redshift > 0: xmin_rest = xmin / (1.0 + obj.redshift) xmax_rest = xmax / (1.0 + obj.redshift) active_drag = None if "mobile" in device or "tablet" in device else "box_zoom" tools = ( "box_zoom, pan, reset" if "mobile" in device or "tablet" in device else "box_zoom,wheel_zoom,pan,reset" ) # These values are equivalent from the photometry plot values frame_width = width - 64 if device == "mobile_portrait": legend_items_per_row = 1 legend_row_height = 24 aspect_ratio = 1 elif device == "mobile_landscape": legend_items_per_row = 4 legend_row_height = 50 aspect_ratio = 1.8 elif device == "tablet_portrait": legend_items_per_row = 5 legend_row_height = 50 aspect_ratio = 1.5 elif device == "tablet_landscape": legend_items_per_row = 7 legend_row_height = 50 aspect_ratio = 1.8 elif device == "browser": frame_width = width - 200 plot_height = ( 400 if device == "browser" else math.floor(width / aspect_ratio) + legend_row_height * int(len(split) / legend_items_per_row) + 30 # 30 is the height of the toolbar ) plot = figure( frame_width=frame_width, height=plot_height, y_range=(ymin, ymax), x_range=(xmin, xmax), tools=tools, toolbar_location="above", active_drag=active_drag, ) plot.add_tools(hover) model_dict = {} legend_items = [] for i, (key, df) in enumerate(split): renderers = [] s = Spectrum.query.get(key) label = f'{s.instrument.name} ({s.observed_at.date().strftime("%m/%d/%y")})' model_dict['s' + str(i)] = plot.step( x='wavelength', y='flux', color=color_map[key], source=ColumnDataSource(df), ) renderers.append(model_dict['s' + str(i)]) legend_items.append(LegendItem(label=label, renderers=renderers)) model_dict['l' + str(i)] = plot.line( x='wavelength', y='flux', color=color_map[key], source=ColumnDataSource(df), line_alpha=0.0, ) plot.xaxis.axis_label = 'Wavelength (Å)' plot.yaxis.axis_label = 'Flux' plot.toolbar.logo = None if obj.redshift is not None and obj.redshift > 0: plot.extra_x_ranges = {"rest_wave": Range1d(start=xmin_rest, end=xmax_rest)} plot.add_layout( LinearAxis(x_range_name="rest_wave", axis_label="Rest Wavelength (Å)"), 'above', ) # TODO how to choose a good default? plot.y_range = Range1d(0, 1.03 * data.flux.max()) legend_loc = "below" if "mobile" in device or "tablet" in device else "right" legend_orientation = ( "vertical" if device in ["browser", "mobile_portrait"] else "horizontal" ) add_plot_legend(plot, legend_items, width, legend_orientation, legend_loc) # 20 is for padding slider_width = width if "mobile" in device else int(width / 2) - 20 z_title = Div(text="Redshift (<i>z</i>): ") z_slider = Slider( value=obj.redshift if obj.redshift is not None else 0.0, start=0.0, end=3.0, step=0.001, show_value=False, format="0[.]000", ) z_textinput = TextInput( value=str(obj.redshift if obj.redshift is not None else 0.0) ) z_slider.js_on_change( 'value', CustomJS( args={'slider': z_slider, 'textinput': z_textinput}, code=""" textinput.value = parseFloat(slider.value).toFixed(3); textinput.change.emit(); """, ), ) z = column( z_title, z_slider, z_textinput, width=slider_width, margin=(4, 10, 0, 10), ) v_title = Div(text="<i>V</i><sub>expansion</sub> (km/s): ") v_exp_slider = Slider( value=0.0, start=0.0, end=3e4, step=10.0, show_value=False, ) v_exp_textinput = TextInput(value='0') v_exp_slider.js_on_change( 'value', CustomJS( args={'slider': v_exp_slider, 'textinput': v_exp_textinput}, code=""" textinput.value = parseFloat(slider.value).toFixed(0); textinput.change.emit(); """, ), ) v_exp = column( v_title, v_exp_slider, v_exp_textinput, width=slider_width, margin=(0, 10, 0, 10), ) for i, (wavelengths, color) in enumerate(SPEC_LINES.values()): el_data = pd.DataFrame({'wavelength': wavelengths}) obj_redshift = 0 if obj.redshift is None else obj.redshift el_data['x'] = el_data['wavelength'] * (1.0 + obj_redshift) model_dict[f'el{i}'] = plot.segment( x0='x', x1='x', # TODO change limits y0=0, y1=1e4, color=color, source=ColumnDataSource(el_data), ) model_dict[f'el{i}'].visible = False # Split spectral line legend into columns if device == "mobile_portrait": columns = 3 elif device == "mobile_landscape": columns = 5 else: columns = 7 element_dicts = zip(*itertools.zip_longest(*[iter(SPEC_LINES.items())] * columns)) elements_groups = [] # The Bokeh checkbox groups callbacks = [] # The checkbox callbacks for each element for column_idx, element_dict in enumerate(element_dicts): element_dict = [e for e in element_dict if e is not None] labels = [key for key, value in element_dict] colors = [c for key, (w, c) in element_dict] elements = CheckboxWithLegendGroup( labels=labels, active=[], colors=colors, width=width // (columns + 1) ) elements_groups.append(elements) callback = CustomJS( args={ 'elements': elements, 'z': z_textinput, 'v_exp': v_exp_textinput, **model_dict, }, code=f""" let c = 299792.458; // speed of light in km / s const i_max = {column_idx} + {columns} * elements.labels.length; let local_i = 0; for (let i = {column_idx}; i < i_max; i = i + {columns}) {{ let el = eval("el" + i); el.visible = (elements.active.includes(local_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(); local_i++; }} """, ) elements.js_on_click(callback) callbacks.append(callback) z_textinput.js_on_change( 'value', CustomJS( args={ 'z': z_textinput, 'slider': z_slider, 'v_exp': v_exp_textinput, **model_dict, }, code=""" // Update slider value to match text input slider.value = parseFloat(z.value).toFixed(3); """, ), ) v_exp_textinput.js_on_change( 'value', CustomJS( args={ 'z': z_textinput, 'slider': v_exp_slider, 'v_exp': v_exp_textinput, **model_dict, }, code=""" // Update slider value to match text input slider.value = parseFloat(v_exp.value).toFixed(3); """, ), ) # Update the element spectral lines as well for callback in callbacks: z_textinput.js_on_change('value', callback) v_exp_textinput.js_on_change('value', callback) # Add some height for the checkboxes and sliders if device == "mobile_portrait": height = plot_height + 400 elif device == "mobile_landscape": height = plot_height + 350 else: height = plot_height + 200 row2 = row(elements_groups) row3 = column(z, v_exp) if "mobile" in device else row(z, v_exp) layout = column( plot, row2, row3, sizing_mode='stretch_width', width=width, height=height, ) return bokeh_embed.json_item(layout)
offset_slider = Slider(start=-5, end=5, value=0, step=.1, title="Offset") callback = CustomJS(args=dict(source=source, amp=amp_slider, freq=freq_slider, phase=phase_slider, offset=offset_slider), code=""" const data = source.data; const A = amp.value; const k = freq.value; const phi = phase.value; const B = offset.value; const x = data['x'] const y = data['y'] for (var i = 0; i < x.length; i++) { y[i] = B + A*Math.sin(k*x[i]+phi); } source.change.emit(); """) amp_slider.js_on_change('value', callback) freq_slider.js_on_change('value', callback) phase_slider.js_on_change('value', callback) offset_slider.js_on_change('value', callback) layout = row( plot, column(amp_slider, freq_slider, phase_slider, offset_slider), ) output_file("slider.html", title="slider.py example") show(layout)
def photometry_plot(obj_id, user, width=600, device="browser"): """Create object photometry scatter plot. Parameters ---------- obj_id : str ID of Obj to be plotted. Returns ------- dict Returns Bokeh JSON embedding for the desired plot. """ data = pd.read_sql( DBSession() .query( Photometry, Telescope.nickname.label("telescope"), Instrument.name.label("instrument"), ) .join(Instrument, Instrument.id == Photometry.instrument_id) .join(Telescope, Telescope.id == Instrument.telescope_id) .filter(Photometry.obj_id == obj_id) .filter( Photometry.groups.any(Group.id.in_([g.id for g in user.accessible_groups])) ) .statement, DBSession().bind, ) if data.empty: return None, None, None # get spectra to annotate on phot plots spectra = ( Spectrum.query_records_accessible_by(user) .filter(Spectrum.obj_id == obj_id) .all() ) data['color'] = [get_color(f) for f in data['filter']] # get marker for each unique instrument instruments = list(data.instrument.unique()) markers = [] for i, inst in enumerate(instruments): markers.append(phot_markers[i % len(phot_markers)]) filters = list(set(data['filter'])) colors = [get_color(f) for f in filters] color_mapper = CategoricalColorMapper(factors=filters, palette=colors) color_dict = {'field': 'filter', 'transform': color_mapper} labels = [] for i, datarow in data.iterrows(): label = f'{datarow["instrument"]}/{datarow["filter"]}' if datarow['origin'] is not None: label += f'/{datarow["origin"]}' labels.append(label) data['label'] = labels data['zp'] = PHOT_ZP data['magsys'] = 'ab' data['alpha'] = 1.0 data['lim_mag'] = ( -2.5 * np.log10(data['fluxerr'] * PHOT_DETECTION_THRESHOLD) + data['zp'] ) # Passing a dictionary to a bokeh datasource causes the frontend to die, # deleting the dictionary column fixes that del data['original_user_data'] # keep track of things that are only upper limits data['hasflux'] = ~data['flux'].isna() # calculate the magnitudes - a photometry point is considered "significant" # or "detected" (and thus can be represented by a magnitude) if its snr # is above PHOT_DETECTION_THRESHOLD obsind = data['hasflux'] & ( data['flux'].fillna(0.0) / data['fluxerr'] >= PHOT_DETECTION_THRESHOLD ) data.loc[~obsind, 'mag'] = None data.loc[obsind, 'mag'] = -2.5 * np.log10(data[obsind]['flux']) + PHOT_ZP # calculate the magnitude errors using standard error propagation formulae # https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulae data.loc[~obsind, 'magerr'] = None coeff = 2.5 / np.log(10) magerrs = np.abs(coeff * data[obsind]['fluxerr'] / data[obsind]['flux']) data.loc[obsind, 'magerr'] = magerrs data['obs'] = obsind data['stacked'] = False split = data.groupby('label', sort=False) finite = np.isfinite(data['flux']) fdata = data[finite] lower = np.min(fdata['flux']) * 0.95 upper = np.max(fdata['flux']) * 1.05 xmin = data['mjd'].min() - 2 xmax = data['mjd'].max() + 2 # Layout parameters based on device type active_drag = None if "mobile" in device or "tablet" in device else "box_zoom" tools = ( 'box_zoom,pan,reset' if "mobile" in device or "tablet" in device else "box_zoom,wheel_zoom,pan,reset,save" ) legend_loc = "below" if "mobile" in device or "tablet" in device else "right" legend_orientation = ( "vertical" if device in ["browser", "mobile_portrait"] else "horizontal" ) # Compute a plot component height based on rough number of legend rows added below the plot # Values are based on default sizing of bokeh components and an estimate of how many # legend items would fit on the average device screen. Note that the legend items per # row is computed more exactly later once labels are extracted from the data (with the # add_plot_legend() function). # # The height is manually computed like this instead of using built in aspect_ratio/sizing options # because with the new Interactive Legend approach (instead of the legacy CheckboxLegendGroup), the # Legend component is considered part of the plot and plays into the sizing computations. Since the # number of items in the legend can alter the needed heights of the plot, using built-in Bokeh options # for sizing does not allow for keeping the actual graph part of the plot at a consistent aspect ratio. # # For the frame width, by default we take the desired plot width minus 64 for the y-axis/label taking # up horizontal space frame_width = width - 64 if device == "mobile_portrait": legend_items_per_row = 1 legend_row_height = 24 aspect_ratio = 1 elif device == "mobile_landscape": legend_items_per_row = 4 legend_row_height = 50 aspect_ratio = 1.8 elif device == "tablet_portrait": legend_items_per_row = 5 legend_row_height = 50 aspect_ratio = 1.5 elif device == "tablet_landscape": legend_items_per_row = 7 legend_row_height = 50 aspect_ratio = 1.8 elif device == "browser": # Width minus some base width for the legend, which is only a column to the right # for browser mode frame_width = width - 200 height = ( 500 if device == "browser" else math.floor(width / aspect_ratio) + legend_row_height * int(len(split) / legend_items_per_row) + 30 # 30 is the height of the toolbar ) plot = figure( frame_width=frame_width, height=height, active_drag=active_drag, tools=tools, toolbar_location='above', toolbar_sticky=True, y_range=(lower, upper), min_border_right=16, x_axis_location='above', sizing_mode="stretch_width", ) plot.xaxis.axis_label = 'MJD' now = Time.now().mjd plot.extra_x_ranges = {"Days Ago": Range1d(start=now - xmin, end=now - xmax)} plot.add_layout(LinearAxis(x_range_name="Days Ago", axis_label="Days Ago"), 'below') imhover = HoverTool(tooltips=tooltip_format) imhover.renderers = [] plot.add_tools(imhover) model_dict = {} legend_items = [] for i, (label, sdf) in enumerate(split): renderers = [] # for the flux plot, we only show things that have a flux value df = sdf[sdf['hasflux']] key = f'obs{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker=factor_mark('instrument', markers, instruments), fill_color=color_dict, alpha='alpha', source=ColumnDataSource(df), ) renderers.append(model_dict[key]) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='flux', color='color', marker=factor_mark('instrument', markers, instruments), fill_color=color_dict, source=ColumnDataSource( data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], stacked=[], instrument=[], ) ), ) renderers.append(model_dict[key]) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df.iterrows(): px = ro['mjd'] py = ro['flux'] err = ro['fluxerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource( data=dict( xs=y_err_x, ys=y_err_y, color=df['color'], alpha=[1.0] * len(df) ) ), ) renderers.append(model_dict[key]) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', # legend_label=label, source=ColumnDataSource(data=dict(xs=[], ys=[], color=[])), ) renderers.append(model_dict[key]) legend_items.append(LegendItem(label=label, renderers=renderers)) if device == "mobile_portrait": plot.xaxis.ticker.desired_num_ticks = 5 plot.yaxis.axis_label = 'Flux (μJy)' plot.toolbar.logo = None add_plot_legend(plot, legend_items, width, legend_orientation, legend_loc) slider = Slider( start=0.0, end=15.0, value=0.0, step=1.0, title='Binsize (days)', max_width=350, margin=(4, 10, 0, 10), ) callback = CustomJS( args={'slider': slider, 'n_labels': len(split), **model_dict}, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackf.js') ) .read() .replace('default_zp', str(PHOT_ZP)) .replace('detect_thresh', str(PHOT_DETECTION_THRESHOLD)), ) slider.js_on_change('value', callback) # Mark the first and last detections detection_dates = data[data['hasflux']]['mjd'] if len(detection_dates) > 0: first = round(detection_dates.min(), 6) last = round(detection_dates.max(), 6) first_color = "#34b4eb" last_color = "#8992f5" midpoint = (upper + lower) / 2 line_top = 5 * upper - 4 * midpoint line_bottom = 5 * lower - 4 * midpoint y = np.linspace(line_bottom, line_top, num=5000) first_r = plot.line( x=np.full(5000, first), y=y, line_alpha=0.5, line_color=first_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("First detection", f'{first}')], renderers=[first_r], ) ) last_r = plot.line( x=np.full(5000, last), y=y, line_alpha=0.5, line_color=last_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("Last detection", f'{last}')], renderers=[last_r], ) ) # Mark when spectra were taken annotate_spec(plot, spectra, lower, upper) layout = column(slider, plot, width=width, height=height) p1 = Panel(child=layout, title='Flux') # now make the mag light curve ymax = ( np.nanmax( ( np.nanmax(data.loc[obsind, 'mag']) if any(obsind) else np.nan, np.nanmax(data.loc[~obsind, 'lim_mag']) if any(~obsind) else np.nan, ) ) + 0.1 ) ymin = ( np.nanmin( ( np.nanmin(data.loc[obsind, 'mag']) if any(obsind) else np.nan, np.nanmin(data.loc[~obsind, 'lim_mag']) if any(~obsind) else np.nan, ) ) - 0.1 ) plot = figure( frame_width=frame_width, height=height, active_drag=active_drag, tools=tools, y_range=(ymax, ymin), x_range=(xmin, xmax), toolbar_location='above', toolbar_sticky=True, x_axis_location='above', sizing_mode="stretch_width", ) plot.xaxis.axis_label = 'MJD' now = Time.now().mjd plot.extra_x_ranges = {"Days Ago": Range1d(start=now - xmin, end=now - xmax)} plot.add_layout(LinearAxis(x_range_name="Days Ago", axis_label="Days Ago"), 'below') obj = DBSession().query(Obj).get(obj_id) if obj.dm is not None: plot.extra_y_ranges = { "Absolute Mag": Range1d(start=ymax - obj.dm, end=ymin - obj.dm) } plot.add_layout( LinearAxis(y_range_name="Absolute Mag", axis_label="m - DM"), 'right' ) # Mark the first and last detections again detection_dates = data[obsind]['mjd'] if len(detection_dates) > 0: first = round(detection_dates.min(), 6) last = round(detection_dates.max(), 6) midpoint = (ymax + ymin) / 2 line_top = 5 * ymax - 4 * midpoint line_bottom = 5 * ymin - 4 * midpoint y = np.linspace(line_bottom, line_top, num=5000) first_r = plot.line( x=np.full(5000, first), y=y, line_alpha=0.5, line_color=first_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("First detection", f'{first}')], renderers=[first_r], ) ) last_r = plot.line( x=np.full(5000, last), y=y, line_alpha=0.5, line_color=last_color, line_width=2, ) plot.add_tools( HoverTool( tooltips=[("Last detection", f'{last}')], renderers=[last_r], point_policy='follow_mouse', ) ) # Mark when spectra were taken annotate_spec(plot, spectra, ymax, ymin) imhover = HoverTool(tooltips=tooltip_format) imhover.renderers = [] plot.add_tools(imhover) model_dict = {} # Legend items are individually stored instead of being applied # directly when plotting so that they can be separated into multiple # Legend() components if needed (to simulate horizontal row wrapping). # This is necessary because Bokeh does not support row wrapping with # horizontally-oriented legends out-of-the-box. legend_items = [] for i, (label, df) in enumerate(split): renderers = [] key = f'obs{i}' model_dict[key] = plot.scatter( x='mjd', y='mag', color='color', marker=factor_mark('instrument', markers, instruments), fill_color=color_dict, alpha='alpha', source=ColumnDataSource(df[df['obs']]), ) renderers.append(model_dict[key]) imhover.renderers.append(model_dict[key]) unobs_source = df[~df['obs']].copy() unobs_source.loc[:, 'alpha'] = 0.8 key = f'unobs{i}' model_dict[key] = plot.scatter( x='mjd', y='lim_mag', color=color_dict, marker='inverted_triangle', fill_color='white', line_color='color', alpha='alpha', source=ColumnDataSource(unobs_source), ) renderers.append(model_dict[key]) imhover.renderers.append(model_dict[key]) key = f'bin{i}' model_dict[key] = plot.scatter( x='mjd', y='mag', color=color_dict, marker=factor_mark('instrument', markers, instruments), fill_color='color', source=ColumnDataSource( data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[], ) ), ) renderers.append(model_dict[key]) imhover.renderers.append(model_dict[key]) key = 'obserr' + str(i) y_err_x = [] y_err_y = [] for d, ro in df[df['obs']].iterrows(): px = ro['mjd'] py = ro['mag'] err = ro['magerr'] y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', source=ColumnDataSource( data=dict( xs=y_err_x, ys=y_err_y, color=df[df['obs']]['color'], alpha=[1.0] * len(df[df['obs']]), ) ), ) renderers.append(model_dict[key]) key = f'binerr{i}' model_dict[key] = plot.multi_line( xs='xs', ys='ys', color='color', source=ColumnDataSource(data=dict(xs=[], ys=[], color=[])), ) renderers.append(model_dict[key]) key = f'unobsbin{i}' model_dict[key] = plot.scatter( x='mjd', y='lim_mag', color='color', marker='inverted_triangle', fill_color='white', line_color=color_dict, alpha=0.8, source=ColumnDataSource( data=dict( mjd=[], flux=[], fluxerr=[], filter=[], color=[], lim_mag=[], mag=[], magerr=[], instrument=[], stacked=[], ) ), ) imhover.renderers.append(model_dict[key]) renderers.append(model_dict[key]) key = f'all{i}' model_dict[key] = ColumnDataSource(df) key = f'bold{i}' model_dict[key] = ColumnDataSource( df[ [ 'mjd', 'flux', 'fluxerr', 'mag', 'magerr', 'filter', 'zp', 'magsys', 'lim_mag', 'stacked', ] ] ) legend_items.append(LegendItem(label=label, renderers=renderers)) add_plot_legend(plot, legend_items, width, legend_orientation, legend_loc) plot.yaxis.axis_label = 'AB mag' plot.toolbar.logo = None slider = Slider( start=0.0, end=15.0, value=0.0, step=1.0, title='Binsize (days)', max_width=350, margin=(4, 10, 0, 10), ) button = Button(label="Export Bold Light Curve to CSV") button.js_on_click( CustomJS( args={'slider': slider, 'n_labels': len(split), **model_dict}, code=open( os.path.join( os.path.dirname(__file__), '../static/js/plotjs', "download.js" ) ) .read() .replace('objname', obj_id) .replace('default_zp', str(PHOT_ZP)), ) ) # Don't need to expose CSV download on mobile top_layout = ( slider if "mobile" in device or "tablet" in device else row(slider, button) ) callback = CustomJS( args={'slider': slider, 'n_labels': len(split), **model_dict}, code=open( os.path.join(os.path.dirname(__file__), '../static/js/plotjs', 'stackm.js') ) .read() .replace('default_zp', str(PHOT_ZP)) .replace('detect_thresh', str(PHOT_DETECTION_THRESHOLD)), ) slider.js_on_change('value', callback) layout = column(top_layout, plot, width=width, height=height) p2 = Panel(child=layout, title='Mag') # now make period plot # get periods from annotations annotation_list = obj.get_annotations_readable_by(user) period_labels = [] period_list = [] for an in annotation_list: if 'period' in an.data: period_list.append(an.data['period']) period_labels.append(an.origin + ": %.9f" % an.data['period']) if len(period_list) > 0: period = period_list[0] else: period = None # don't generate if no period annotated if period is not None: # bokeh figure for period plotting period_plot = figure( frame_width=frame_width, height=height, active_drag=active_drag, tools=tools, y_range=(ymax, ymin), x_range=(-0.01, 2.01), # initially one phase toolbar_location='above', toolbar_sticky=False, x_axis_location='below', sizing_mode="stretch_width", ) # axis labels period_plot.xaxis.axis_label = 'phase' period_plot.yaxis.axis_label = 'mag' period_plot.toolbar.logo = None # do we have a distance modulus (dm)? obj = DBSession().query(Obj).get(obj_id) if obj.dm is not None: period_plot.extra_y_ranges = { "Absolute Mag": Range1d(start=ymax - obj.dm, end=ymin - obj.dm) } period_plot.add_layout( LinearAxis(y_range_name="Absolute Mag", axis_label="m - DM"), 'right' ) # initiate hover tool period_imhover = HoverTool(tooltips=tooltip_format) period_imhover.renderers = [] period_plot.add_tools(period_imhover) # initiate period radio buttons period_selection = RadioGroup(labels=period_labels, active=0) phase_selection = RadioGroup(labels=["One phase", "Two phases"], active=1) # store all the plot data period_model_dict = {} # iterate over each filter legend_items = [] for i, (label, df) in enumerate(split): renderers = [] # fold x-axis on period in days df['mjd_folda'] = (df['mjd'] % period) / period df['mjd_foldb'] = df['mjd_folda'] + 1.0 # phase plotting for ph in ['a', 'b']: key = 'fold' + ph + f'{i}' period_model_dict[key] = period_plot.scatter( x='mjd_fold' + ph, y='mag', color='color', marker=factor_mark('instrument', markers, instruments), fill_color=color_dict, alpha='alpha', # visible=('a' in ph), source=ColumnDataSource(df[df['obs']]), # only visible data ) # add to hover tool period_imhover.renderers.append(period_model_dict[key]) renderers.append(period_model_dict[key]) # errorbars for phases key = 'fold' + ph + f'err{i}' y_err_x = [] y_err_y = [] # get each visible error value for d, ro in df[df['obs']].iterrows(): px = ro['mjd_fold' + ph] py = ro['mag'] err = ro['magerr'] # set up error tuples y_err_x.append((px, px)) y_err_y.append((py - err, py + err)) # plot phase errors period_model_dict[key] = period_plot.multi_line( xs='xs', ys='ys', color='color', alpha='alpha', # visible=('a' in ph), source=ColumnDataSource( data=dict( xs=y_err_x, ys=y_err_y, color=df[df['obs']]['color'], alpha=[1.0] * len(df[df['obs']]), ) ), ) renderers.append(period_model_dict[key]) legend_items.append(LegendItem(label=label, renderers=renderers)) add_plot_legend( period_plot, legend_items, width, legend_orientation, legend_loc ) # set up period adjustment text box period_title = Div(text="Period (days): ") period_textinput = TextInput(value=str(period if period is not None else 0.0)) period_textinput.js_on_change( 'value', CustomJS( args={ 'textinput': period_textinput, 'numphases': phase_selection, 'n_labels': len(split), 'p': period_plot, **period_model_dict, }, code=open( os.path.join( os.path.dirname(__file__), '../static/js/plotjs', 'foldphase.js' ) ).read(), ), ) # a way to modify the period period_double_button = Button(label="*2", width=30) period_double_button.js_on_click( CustomJS( args={'textinput': period_textinput}, code=""" const period = parseFloat(textinput.value); textinput.value = parseFloat(2.*period).toFixed(9); """, ) ) period_halve_button = Button(label="/2", width=30) period_halve_button.js_on_click( CustomJS( args={'textinput': period_textinput}, code=""" const period = parseFloat(textinput.value); textinput.value = parseFloat(period/2.).toFixed(9); """, ) ) # a way to select the period period_selection.js_on_click( CustomJS( args={'textinput': period_textinput, 'periods': period_list}, code=""" textinput.value = parseFloat(periods[this.active]).toFixed(9); """, ) ) phase_selection.js_on_click( CustomJS( args={ 'textinput': period_textinput, 'numphases': phase_selection, 'n_labels': len(split), 'p': period_plot, **period_model_dict, }, code=open( os.path.join( os.path.dirname(__file__), '../static/js/plotjs', 'foldphase.js' ) ).read(), ) ) # layout if device == "mobile_portrait": period_controls = column( row( period_title, period_textinput, period_double_button, period_halve_button, width=width, sizing_mode="scale_width", ), phase_selection, period_selection, width=width, ) # Add extra height to plot based on period control components added # 18 is the height of each period selection radio option (per default font size) # and the 130 encompasses the other components which are consistent no matter # the data size. height += 130 + 18 * len(period_labels) else: period_controls = column( row( period_title, period_textinput, period_double_button, period_halve_button, phase_selection, width=width, sizing_mode="scale_width", ), period_selection, margin=10, ) # Add extra height to plot based on period control components added # Numbers are derived in similar manner to the "mobile_portrait" case above height += 90 + 18 * len(period_labels) period_layout = column(period_plot, period_controls, width=width, height=height) # Period panel p3 = Panel(child=period_layout, title='Period') # tabs for mag, flux, period tabs = Tabs(tabs=[p2, p1, p3], width=width, height=height, sizing_mode='fixed') else: # tabs for mag, flux tabs = Tabs(tabs=[p2, p1], width=width, height=height + 90, sizing_mode='fixed') return bokeh_embed.json_item(tabs)
from bokeh.models import CustomJS, ColumnDataSource, Slider from bokeh.plotting import Figure, output_file, show output_file("js_on_change.html") x = [x*0.005 for x in range(0, 200)] y = x source = ColumnDataSource(data=dict(x=x, y=y)) plot = Figure(plot_width=400, plot_height=400) plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6) callback = CustomJS(args=dict(source=source), code=""" var data = source.data; var f = cb_obj.value x = data['x'] y = data['y'] for (i = 0; i < x.length; i++) { y[i] = Math.pow(x[i], f) } source.trigger('change'); """) slider = Slider(start=0.1, end=4, value=1, step=.1, title="power") slider.js_on_change('value', callback) layout = column(slider, plot) show(layout)
from bokeh.io import output_file, show from bokeh.layouts import column from bokeh.models import CustomJS, Slider, Div para = Div(text="<h1>Slider Values:</h1><p>Slider 1: 0<p>Slider 2: 0<p>Slider 3: 0") s1 = Slider(title="Slider 1 (Continuous)", start=0, end=1000, value=0, step=1, callback_policy="continuous") s2 = Slider(title="Slider 2 (Throttle)", start=0, end=1000, value=0, step=1, callback_policy="throttle", callback_throttle=1000) s3 = Slider(title="Slider 3 (Mouse Up)", start=0, end=1000, value=0, step=1, callback_policy="mouseup") callback = CustomJS(args=dict(para=para, s1=s1, s2=s2, s3=s3), code=""" para.text = "<h1>Slider Values</h1><p>Slider 1: " + s1.value + "<p>Slider 2: " + s2.value + "<p>Slider 3: " + s3.value """) s1.js_on_change('value_throttled', callback) s2.js_on_change('value_throttled', callback) s3.js_on_change('value_throttled', callback) output_file('slider_callback_policy.html') show(column(s1, s2, s3, para))