def plot_daily_timeline(data, **kwargs): import itertools from datetime import timedelta, datetime, date from bokeh.core.enums import Dimensions, StepMode from bokeh.transform import dodge, cumsum from bokeh.plotting import figure from bokeh.models import ColumnDataSource, OpenURL, TapTool from bokeh.models import ResetTool, BoxZoomTool, HoverTool, PanTool, SaveTool from bokeh.models import NumeralTickFormatter, PrintfTickFormatter, Circle from bokeh.models.ranges import Range1d from bokeh import palettes, layouts from datetime import date x_range = kwargs.get('x_range', None) if not x_range: x_range = (date(2020, 2, 1) - timedelta(days=1), date.today() + timedelta(days=1)) y_range = 0, 60 * 24 # Minutes of a day data_source = ColumnDataSource(data) fig = figure(plot_height=500, plot_width=1000, title=f"Cluster timeline", x_axis_type='datetime', y_axis_type='linear', x_range=x_range, y_range=y_range, tools="") fig.rect(y='minutes', x='date', width=timedelta(days=1) / 2, height=1, color='color', source=data_source) from datetime import time def index_to_time(index: int): hours = index // 60 minutes = index % 60 if index == 60 * 24: return time(0, 0) return time(hours, minutes) minutes = list(range(0, 60 * 24 + 1, 3 * 60)) fig.yaxis.ticker = minutes fig.yaxis.major_label_overrides = { m: str(index_to_time(m)) for m in minutes } fig.output_backend = "webgl" fig.toolbar.logo = None hover_tool = HoverTool(tooltips=[('Date', '@date{%F}')], formatters={'date': 'datetime'}, mode='vline') fig.add_tools(hover_tool) fig.add_tools(SaveTool()) fig.add_tools(BoxZoomTool()) fig.add_tools(PanTool()) fig.add_tools(ResetTool()) return fig
SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "black", "red" edge_attrs = {} for start_node, end_node, _ in G.edges(data=True): edge_color = SAME_CLUB_COLOR if G.nodes[start_node]["club"] == G.nodes[ end_node]["club"] else DIFFERENT_CLUB_COLOR edge_attrs[(start_node, end_node)] = edge_color nx.set_edge_attributes(G, edge_attrs, "edge_color") # Show with Bokeh plot = Plot(plot_width=400, plot_height=400, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1)) plot.title.text = "Graph Interaction Demonstration" node_hover_tool = HoverTool(tooltips=[("index", "@index"), ("club", "@club")]) plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool()) graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0)) graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0]) graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color", line_alpha=0.8, line_width=1) plot.renderers.append(graph_renderer) output_file("interactive_graphs.html") show(plot)
df_deep = df[df['depths'] >= 300] eq_info_shallow = ColumnDataSource(df_shallow) eq_info_middle = ColumnDataSource(df_middle) eq_info_deep = ColumnDataSource(df_deep) return (eq_info_shallow, eq_info_middle, eq_info_deep) ## range bounds supplied in web mercator coordinates x_range, y_range = ((-18706892.5544, 21289852.6142), (-7631472.9040, 12797393.0236)) p = figure(width=1280, height=720, x_range=x_range, y_range=y_range) ## Styling p.add_tile(tile_provider) p.tools = [PanTool(), WheelZoomTool(), BoxZoomTool(), ResetTool(), SaveTool()] # hover = HoverTool(tooltips=[("Magnitude","@magnitudes"), ("depths","@depths")]) hover = HoverTool(tooltips=""" <div> <div> <span style="font-size: 12px; font-weight: bold;">@event_text</span> </div> <div> <span style="font-size: 10px; color: #696;">Event time: @event_time</span><br> <span style="font-size: 10px; color: #696;">Coordinates: (@longitudes,@latitudes)</span><br> <span style="font-size: 10px; color: #696;">Magnitude: @magnitudes @magnitude_type</span><br> <span style="font-size: 10px; color: #696;">Depth: @depths km</span> </div> </div> """) p.add_tools(hover)
options=ListLignes, width=200) # Bouton pour faire l'update button_update = Button(label="Changer les dates", width=100) # GRAPHE DES DONNEES # cree un graphe scatter avec tout les elements repartis par heure et jour de la semaine DAYS = ['Dim', 'Sam', 'Ven', 'Jeu', 'Mer', 'Mar', 'Lun'] hover = HoverTool(tooltips=[("Jour", "@Jour"), ( "Ligne", "@NumLigne"), ("Véhicule", "@NumParc"), ("KiKo", "@KiKo"), ("Vitesse", "@SpeedVA_AF")]) TOOLS = [ PanTool(), BoxZoomTool(), LassoSelectTool(), BoxSelectTool(), WheelZoomTool(), ResetTool(), SaveTool(), hover ] Jitter = figure(plot_width=560, plot_height=370, y_range=DAYS, x_axis_type='datetime', tools=TOOLS, output_backend="webgl") Jitter.xaxis[0].formatter.days = ['%Hh'] Jitter.x_range.range_padding = 0
def make_graph_plot(self): """ Builds the graph portion of the final model from the DAG constructed by make_graph. """ nodes = nx.nx_pydot.graphviz_layout(self._graph, prog="dot") node_x, node_y = zip(*nodes.values()) models = [self._graph.nodes[x]["model"] for x in nodes] node_id = list(nodes.keys()) node_source = ColumnDataSource({ "x": node_x, "y": node_y, "index": node_id, "model": models }) edge_x_coords = [] edge_y_coords = [] for start_node, end_node in self._graph.edges: edge_x_coords.extend([[nodes[start_node][0], nodes[end_node][0]]]) edge_y_coords.extend([[nodes[start_node][1], nodes[end_node][1]]]) edge_source = ColumnDataSource({ "xs": edge_x_coords, "ys": edge_y_coords }) p2 = Plot(outline_line_alpha=0.0) xinterval = max(max(node_x) - min(node_x), 200) yinterval = max(max(node_y) - min(node_y), 200) p2.x_range = Range1d(start=min(node_x) - 0.15 * xinterval, end=max(node_x) + 0.15 * xinterval) p2.y_range = Range1d(start=min(node_y) - 0.15 * yinterval, end=max(node_y) + 0.15 * yinterval) node_renderer = GlyphRenderer( data_source=node_source, glyph=Circle(x="x", y="y", size=15, fill_color="lightblue"), nonselection_glyph=Circle(x="x", y="y", size=15, fill_color="lightblue"), selection_glyph=Circle(x="x", y="y", size=15, fill_color="green"), ) edge_renderer = GlyphRenderer(data_source=edge_source, glyph=MultiLine(xs="xs", ys="ys")) node_hover_tool = HoverTool(tooltips=[("id", "@index"), ("model", "@model")]) node_hover_tool.renderers = [node_renderer] tap_tool = TapTool() tap_tool.renderers = [node_renderer] labels = LabelSet( x="x", y="y", text="model", source=node_source, text_font_size="8pt", x_offset=-20, y_offset=7, ) help = Label( x=20, y=20, x_units="screen", y_units="screen", text_font_size="8pt", text_font_style="italic", text="Click on a model to see its attributes", ) p2.add_layout(help) p2.add_layout(edge_renderer) p2.add_layout(node_renderer) p2.tools.extend( [node_hover_tool, tap_tool, BoxZoomTool(), ResetTool(), PanTool()]) p2.renderers.append(labels) self._node_source = node_source self._edge_source = edge_source return p2
waveplot = figure(plot_width=TSPLOTW, plot_height=TSPLOTH, tools=TSTOOLS, x_axis_type='datetime') waveclr = 'black' offset = 0 for s in st: waveplot.line(s.times('timestamp'), s.data / max(s.data) + offset, line_width=2, color=waveclr) offset += 2 waveplot.circle('ontimes', 'y', source=source_triggers, size=10, color='red') #waveplot.circle('offtimes','y', source=source_triggers, size=10, color='blue') waveplot.add_tools(BoxZoomTool(dimensions="width")) # STA/LTA functions cftplot = figure(plot_width=TSPLOTW, plot_height=TSPLOTH, tools=TSTOOLS, x_axis_type='datetime') cftline = cftplot.line('times', 'cft', source=source_stalta, line_width=2, color='black') cftplot.add_tools(BoxZoomTool(dimensions="width")) cftplot.x_range = waveplot.x_range sta_on = Span(location=None, dimension='width',
print(sys.argv) ghfam=sys.argv[1] pkltreename=sys.argv[2] #with open('st.pkl','rb') as f: treefpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),pkltreename) dbfpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),f'{ghfam.upper()}DB.sql') ncfpath=os.path.join(os.environ['SCIENCEDIR'],'GHSeqs',ghfam.upper(),f'{ghfam.lower()}ds.nc') print(treefpath) with open(treefpath,'rb') as f: etetree=pickle.load(f) hover_tool = HoverTool(names=['leaf_node'],tooltips=[ ("GB acc", "@gbacc"),('sf','@subfam'),\ ('phylum','@phylum'),('class','@class'),('species','@species')]) hover_tool2 = HoverTool(names=['metadata'],tooltips=[ ("GB acc", "@gbacc"),("ECs", "@ecs"),('PDBs','@pdbids')]) pheight=1400 p1 = figure(plot_width=850,plot_height=pheight,tools=[hover_tool,ResetTool(),BoxZoomTool(),PanTool()])#plot_width=1100, plot_height=700, #pheight=int(len(ptree.get_leaves())*1.5) p2 = figure(plot_width=60,plot_height=pheight,tools=[hover_tool2],x_range=Range1d(0,2),y_range=p1.y_range) ptree=phylotree.PhyloTree(etetree) ptree.update_leafcdsdict_fromdb(dbfpath) ptree.update_leafcdsdict_fromxr(ncfpath) ptree.bokehdraw(plot=p1,rotation=0) fglyph=MultiLine(xs='frame_xs',ys='frame_ys',line_width='frame_lws') p1.add_glyph(ptree.leaf_cds,fglyph)#,name='leaf_node') p1.add_glyph(ptree.internal_cds,fglyph)#,name='internal_node') qglyph=p1.quad(left='nodebox_lefts',right='nodebox_rights',bottom='nodebox_bottoms',top='nodebox_tops',fill_color='qcolor',line_alpha=0,\ fill_alpha='qfillalpha',hatch_pattern='qhatch',source=ptree.leaf_cds,name='leaf_node')#,fill_alpha=0,line_alpha=0) #qglyph=Quad(left='nodebox_lefts',right='nodebox_rights',bottom='nodebox_bottoms',top='nodebox_tops',fill_color='qcolor',line_alpha=0,\ # hatch_pattern='qhatch')#,fill_alpha=0,line_alpha=0) #p1.add_glyph(ptree.leaf_cds,qglyph,name='leaf_node')#'leaf_node')#self.ntype)
bldgType=[], bldgSubtype=[], city=[], zipcode=[], state=[], gsf=[], score=[])) hover = HoverTool(tooltips=[ ("Name", "@facilityName"), ("Owner", "@owner"), ("Score", "@score"), ("Type", "@bldgSubtype"), ("City", "@city"), ("State", "@state"), ("Zip", "@zipcode"), ("GSF", "@gsf"+" sq ft"), ]) p = figure(plot_height=800, plot_width=700, title="", toolbar_location="above", tools=[hover,BoxZoomTool(),ResetTool()]) p.circle(x="x", y="y", source=source, size="dotSize", color="dotColor", line_color="lineColor", line_width="lineWidth", fill_alpha="alpha") p.axis.axis_label_text_font_style = "bold" p.axis.major_label_text_font_style = "bold" p.axis.axis_label_text_font_size = "16pt" p.axis.major_label_text_font_size = "14pt" p.xaxis.formatter = NumeralTickFormatter(format="0") p.yaxis.formatter = NumeralTickFormatter(format="0") p.title.text_font_size = "16pt" # 6 different line colors, 3 different line widths, 3 different line dashes #line_color #line_width #line_dash
def wsgraph(user_node, nodes_connect, prob): WS = nx.watts_strogatz_graph(int(user_node), int(nodes_connect), float(prob)) degrees = WS.degree() nodes = WS.nodes() if user_node <= 10 and nodes_connect <= 10: multisize = 2 elif user_node <= 20 and nodes_connect <= 20: multisize = 1.5 elif user_node <= 30 and nodes_connect <= 30: multisize = 1 elif user_node <= 50 and nodes_connect <= 50: multisize = .5 else: multisize = .25 eigenv = nx.eigenvector_centrality(WS, weight=None) between = nx.betweenness_centrality(WS, k=user_node, weight=None) edges = list(nx.edges(WS)) edge_end = [edge[1] for edge in edges] node_color = {k: v for k, v in WS.degree()} node_size = {k: multisize * v for k, v in WS.degree()} nx.set_node_attributes(WS, node_color, 'node_color') nx.set_node_attributes(WS, node_size, 'node_size') mapper = LinearColorMapper(palette=Purples9, low=user_node, high=0) connections = list(node_color.values()) eigenv = list(eigenv.values()) between = list(between.values()) source = ColumnDataSource( pd.DataFrame.from_dict({k: v for k, v in WS.nodes(data=True)}, orient='index')) source.add(connections, "connections") source.add(eigenv, "eigenv") source.add(between, "between") boxcolor = BoxAnnotation(fill_color='white', line_color='black') plot_option = { 'background_fill_color': 'gray', 'background_fill_alpha': .5, 'tools': [ PanTool(), WheelZoomTool(), TapTool(), BoxZoomTool(overlay=boxcolor), BoxSelectTool(overlay=boxcolor), ResetTool() ], 'x_range': Range1d(-2, 2), 'y_range': Range1d(-2, 2), } plot = figure(**plot_option, x_axis_location=None, y_axis_location=None) plot.title.text = "Watts-Strogatz Graph" graph = from_networkx(WS, nx.circular_layout) graph.node_renderer.data_source = source graph.node_renderer.glyph = Circle(name="Node", size='node_size', fill_color={ 'field': 'node_color', 'transform': mapper }) graph.edge_renderer.selection_glyph = MultiLine(line_color='white', line_width=3) plot.add_tools( HoverTool(name="Node", tooltips=[ ('Node', '$index'), ('Connections', '@connections'), ('Eigen Value', '@eigenv{.000000}'), ('Betweenness', '@between{.000000}'), ])) graph.selection_policy = NodesAndLinkedEdges() plot.toolbar.active_scroll = plot.select_one(WheelZoomTool) plot.toolbar.autohide = True plot.renderers.append(graph) return plot
def plotWeightsAndTrips(): weights_y = None # Weights and dates df_weights = pd.read_sql_query( "SELECT * FROM weights ORDER BY weight_date", app.config['SQLALCHEMY_DATABASE_URI']) weightDates_x = df_weights['weight_date'] weights_y = df_weights['weight'] line_color = "black" line_width = 4 df_solo_trips = pd.read_sql_query( "select * from trips where passenger_companion = '' ", app.config['SQLALCHEMY_DATABASE_URI']) df_companion_trips = pd.read_sql_query( "select * from trips where passenger_companion != '' ", app.config['SQLALCHEMY_DATABASE_URI']) if (df_solo_trips.empty == True and df_companion_trips.empty == True and df_weights.empty == True): return ["", "", ""] # Make sure we have a default bar height # in case there is no Weight data to show weights_y = weights_y if weights_y is not None else [0, 9] highestWeight_y = int(sorted(weights_y)[-1]) lowestWeight_y = int(sorted(weights_y)[0]) # tripsBarHeight = height of the vertical bar # which depends on max height of weights_y # + 10% # tripsBarStart = left start of the bar # tripsBarEnd = right end of the bar # tripsBarColors = global if one "color", # individual if an array # ["color_1", "color_2" [,...]] # Solo trips horizontal bars data tripsBarHeight = (highestWeight_y - lowestWeight_y ) + (highestWeight_y - lowestWeight_y) * 0.1 tripsBarStart = df_solo_trips['departure_date'] tripsBarEnd = df_solo_trips['return_date'] tripsBarColors = "red" # Or individual colors for each value ["Cyan", "red",...] # Group trips horizontal bars data tripsBarGroupStart = df_companion_trips['departure_date'] tripsBarGroupEnd = df_companion_trips['return_date'] tripsBarGroupColors = "blue" # designing the plot style and information hover = HoverTool( show_arrow=True, point_policy='follow_mouse', tooltips=[("Trip from", " @left{%F}"), ("to", " @right{%F}"), ("Date", " @x{%F}"), ("Weight", " @y Kg")], formatters={ # use 'datetime' formatter for 'date' fields # default 'numeral' formatter for other fields '@x': 'datetime', '@left': 'datetime', '@right': 'datetime' }) box = BoxSelectTool() wheel = WheelZoomTool() panTool = PanTool() boxZoomTool = BoxZoomTool() resetTool = ResetTool() TOOLS = [hover, box, wheel, panTool, boxZoomTool, resetTool] _plot = figure(title="Personal data registered since June 2014.", x_axis_label='Dates', x_axis_type='datetime', y_axis_label='Kilograms', y_axis_type='auto', plot_width=650, plot_height=450, sizing_mode='scale_both', tools=TOOLS) _plot.hbar(name="red", y=highestWeight_y * 0.96, height=tripsBarHeight, left=tripsBarStart, right=tripsBarEnd, color=tripsBarColors, legend_label="Solo trips") _plot.hbar(name="blue", y=highestWeight_y * 0.96, height=tripsBarHeight, left=tripsBarGroupStart, right=tripsBarGroupEnd, color=tripsBarGroupColors, legend_label="Group trips") # add weights' line to the figure _plot.line(weightDates_x, weights_y, color=line_color, width=line_width, legend_label="Weights", line_width=5) # Technicalities to show the graph curdoc().add_root(_plot) cdn_javascript = CDN.js_files[0] bokehScriptComponent, bokehDivComponent = components(_plot) graph = [cdn_javascript, bokehScriptComponent, bokehDivComponent] return graph
def create_plots(model1, model3, model4, model5, live_data, city, display_name): """ Output: Bokeh plot Creates individual timeseries plot """ if city != 'chicago': model1 = model1.query("city_{} == 1 and display_name_{} == 1".format( city, display_name)) # model2 = model2.query("city_{} == 1 and display_name_{} == 1".format(city, display_name)) model3 = model3.query("city_{} == 1 and display_name_{} == 1".format( city, display_name)) model4 = model4.query("city_{} == 1 and display_name_{} == 1".format( city, display_name)) model5 = model5.query("city_{} == 1 and display_name_{} == 1".format( city, display_name)) else: model1 = model1.query( "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1" .format(display_name)) # model2 = model2.query("city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1".format(display_name)) model3 = model3.query( "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1" .format(display_name)) model4 = model4.query( "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1" .format(display_name)) model5 = model5.query( "city_denver == 0 and city_seattle == 0 and city_sf == 0 and city_ny == 0 and display_name_{} == 1" .format(display_name)) cartype = display_name.lower() live_data = live_data.query("display_name == @cartype and city == @city") source1 = ColumnDataSource(data=dict(d=model1['date'].astype(str), h=model1['hour'], f=model1['y_forecast'], n=model1['name'])) # source2 = ColumnDataSource( # data=dict( # d=model2['date'].astype(str), # h=model2['hour'], # f=model2['y_forecast'], # n=model2['name'] # ) # ) source3 = ColumnDataSource(data=dict(d=model3['date'].astype(str), h=model3['hour'], f=model3['y_forecast'], n=model3['name'])) source4 = ColumnDataSource(data=dict(d=model4['date'].astype(str), h=model4['hour'], f=model4['y_forecast'], n=model4['name'])) source5 = ColumnDataSource(data=dict(d=model5['date'].astype(str), h=model5['hour'], f=model5['y_forecast'], n=model5['name'])) source6 = ColumnDataSource(data=dict(d=live_data['date'].astype(str), h=live_data['hour'], f=live_data['avg_price_est'], n=live_data['name'])) hover = HoverTool(tooltips=[("Model", "@n"), ("Date", "@d"), ("Hour", "@h"), ("Average Price", "@f")]) change_city = { 'denver': 'Denver', 'ny': 'New York', 'chicago': 'Chicago', 'seattle': 'Seattle', 'sf': 'San Francisco' } p = figure(title="Forecast of {} {} Prices - {} to {}".format( change_city[city], display_name, sys.argv[1], sys.argv[2]), plot_width=1000, plot_height=500, x_axis_type="datetime", tools=[ hover, PanTool(), BoxZoomTool(), ResizeTool(), WheelZoomTool(), PreviewSaveTool(), ResetTool() ], toolbar_location="left", title_text_font_size="20pt") p.line(model1['record_time'], model1['y_forecast'], line_color='blue', line_width=2, legend="Random Forest Regressor", alpha=0.5, source=source1) # p.line(model2['record_time'], model2['y_forecast'], line_color='green', line_width=2, legend="RF Model 2 - Without Surge Multiplier", alpha=0.5, source=source2) # line_dash=[4,4] p.line(model3['record_time'], model3['y_forecast'], line_color='magenta', line_width=2, legend="Ridge Regression", alpha=0.5, source=source3) # line_dash=[4,4] p.line(model4['record_time'], model4['y_forecast'], line_color='gray', line_width=2, legend="ARIMA Model", alpha=0.5, source=source4) # line_dash=[4,4] p.line(model5['record_time'], model5['y_forecast'], line_color='green', line_width=2, legend="XGB Regressor", alpha=0.5, source=source5) # line_dash=[4,4] # p.xaxis.axis_label = 'Time' # p.xaxis.axis_label_text_font_size = "10pt" p.yaxis.axis_label = 'Average Price Estimate' p.yaxis.axis_label_text_font_size = "20pt" p.yaxis.axis_label_standoff = 15 p.xgrid[0].ticker.desired_num_ticks = 20 xformatter = DatetimeTickFormatter(formats=dict(hours=["%H"])) p.xaxis.formatter = xformatter p.legend.label_text_font_size = "10pt" # add a text renderer to out plot (no data yet) r = p.circle(x=live_data['record_time'], y=live_data['avg_price_est'], legend="True Average Prices", source=source6, color='red') ds = r.data_source return p, ds
def interactive_plot(network, network_name, layout_func='fruchterman_reingold'): plot = Plot(plot_width=800, plot_height=800, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1)) plot.title.text = network_name plot.add_tools( HoverTool( tooltips=[('bird', '@index'), ("age", "@Age"), ("sex", "@Sex"), ("location", '@Location')]), TapTool(), BoxSelectTool(), BoxZoomTool(), ResetTool(), PanTool(), WheelZoomTool()) if layout_func == 'fruchterman_reingold': graph_renderer = graphs.from_networkx(network, nx.fruchterman_reingold_layout, scale=1, center=(0, 0)) elif layout_func == 'spring': graph_renderer = graphs.from_networkx(network, nx.spring_layout, scale=1, center=(0, 0)) elif layout_func == 'circular': graph_renderer = graphs.from_networkx(network, nx.circular_layout, scale=1, center=(0, 0)) elif layout_func == 'kamada': graph_renderer = graphs.from_networkx(network, nx.kamada_kawai_layout, scale=1, center=(0, 0)) elif layout_func == 'spectral': graph_renderer = graphs.from_networkx(network, nx.spectral_layout, scale=1, center=(0, 0)) else: graph_renderer = graphs.from_networkx(network, nx.fruchterman_reingold_layout, scale=1, center=(0, 0)) centrality = nx.algorithms.centrality.betweenness_centrality(network) """ first element are nodes again """ _, nodes_centrality = zip(*centrality.items()) max_centraliy = max(nodes_centrality) c_centrality = [7 + 15 * t / max_centraliy for t in nodes_centrality] import community #python-louvain partition = community.best_partition(network) p_, nodes_community = zip(*partition.items()) community_colors = [ '#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666' ] colors = [ community_colors[t % len(community_colors)] for t in nodes_community ] _, Sex = zip(*nx.get_node_attributes(network, 'Sex').items()) _, Age = zip(*nx.get_node_attributes(network, 'Age').items()) _, Location = zip(*nx.get_node_attributes(network, 'Location').items()) graph_renderer.node_renderer.data_source.add(c_centrality, 'centrality') graph_renderer.node_renderer.data_source.add(Sex, 'Sex') graph_renderer.node_renderer.data_source.add(Age, 'Age') graph_renderer.node_renderer.data_source.add(Location, 'Location') graph_renderer.node_renderer.data_source.add(colors, 'colors') graph_renderer.node_renderer.glyph = Circle(size='centrality', fill_color='colors') graph_renderer.node_renderer.selection_glyph = Circle( size='centrality', fill_color=Spectral4[2]) graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral4[1]) _, edge_weights = zip(*nx.get_edge_attributes(network, 'weight').items()) max_weights = max(edge_weights) c_weights = [7 + 2 * (t / max_weights) for t in edge_weights] graph_renderer.edge_renderer.data_source.add(c_weights, 'weight') graph_renderer.edge_renderer.glyph = MultiLine(line_color="#757474", line_alpha=0.2, line_width='weight') graph_renderer.edge_renderer.selection_glyph = MultiLine( line_color=Spectral4[2], line_width='weight') graph_renderer.edge_renderer.hover_glyph = MultiLine( line_color=Spectral4[1], line_width='weight') graph_renderer.selection_policy = graphs.NodesAndLinkedEdges() graph_inspection_policy = graphs.NodesOnly() #graph_renderer.inspection_policy = graphs.EdgesAndLinkedNodes() plot.renderers.append(graph_renderer) #output_file("interactive_graphs.html") return plot
def __make_daybyday_interactive_timeline( df: pd.DataFrame, *, geo_df: geopandas.GeoDataFrame, value_col: str, transform_df_func: Callable[[pd.DataFrame], pd.DataFrame] = None, stage: Union[DiseaseStage, Literal[Select.ALL]] = Select.ALL, count: Union[Counting, Literal[Select.ALL]] = Select.ALL, out_file_basename: str, subplot_title_prefix: str, plot_aspect_ratio: float = None, cmap=None, n_cbar_buckets: int = None, n_buckets_btwn_major_ticks: int = None, n_minor_ticks_btwn_major_ticks: int = None, per_capita_denominator: int = None, x_range: Tuple[float, float], y_range: Tuple[float, float], min_visible_y_range: float, should_make_video: bool, ) -> InfoForAutoload: """Create the bokeh interactive timeline plot(s) This function takes the given DataFrame, which must contain COVID data for locations on different dates, and a GeoDataFrame, which contains the long/lat coords for those locations, and creates an interactive choropleth of the COVID data over time. :param df: The COVID data DataFrame :type df: pd.DataFrame :param geo_df: The geometry GeoDataFrame for the locations in `df` :type geo_df: geopandas.GeoDataFrame :param value_col: The column of `df` containing the values to plot in the choropleth; should be something like "Case_Counts" or "Case_Diff_From_Prev_Day" :type value_col: str :param stage: The DiseaseStage to plot, defaults to Select.ALL. If ALL, then all stages are plotted and are stacked vertically. :type stage: Union[DiseaseStage, Literal[Select.ALL]], optional :param count: The Counting to plot, defaults to Select.ALL. If ALL, then all count types are plotted and are stacked horizontally. :type count: Union[Counting, Literal[Select.ALL]], optional :param out_file_basename: The basename of the file to save the interactive plots to (there are two components, the JS script and the HTML <div>) :type out_file_basename: str :param subplot_title_prefix: What the first part of the subplot title should be; probably a function of `value_col` (if value_col is "Case_Counts" then this param might be "Cases" or "# of Cases") :type subplot_title_prefix: str :param x_range: The range of the x-axis as (min, max) :type x_range: Tuple[float, float] :param y_range: The range of the y-axis as (min, max) :type y_range: Tuple[float, float] :param min_visible_y_range: The minimum height (in axis units) of the y-axis; it will not be possible to zoom in farther than this on the choropleth. :type min_visible_y_range: float :param should_make_video: Optionally run through the timeline day by day, capture a screenshot for each day, and then stitch the screenshots into a video. The video shows the same info as the interactive plots, but not interactively. This easily takes 20x as long as just making the graphs themselves, so use with caution. :type should_make_video: bool :param transform_df_func: This function expects data in a certain format, and does a bunch of preprocessing (expected to be common) before plotting. This gives you a chance to do any customization on the postprocessed df before it's plotted. Defaults to None, in which case no additional transformation is performed. :type transform_df_func: Callable[[pd.DataFrame], pd.DataFrame], optional :param plot_aspect_ratio: The aspect ratio of the plot as width/height; if set, the aspect ratio will be fixed to this. Defaults to None, in which case the aspect ratio is determined from the x_range and y_range arguments :type plot_aspect_ratio: float, optional :param cmap: The colormap to use as either a matplotlib-compatible colormap or a list of hex strings (e.g., ["#ae8f1c", ...]). Defaults to None in which case a reasonable default is used. :type cmap: Matplotlib-compatible colormap or List[str], optional :param n_cbar_buckets: How many colorbar buckets to use. Has little effect if the colormap is continuous, but always works in conjunction with n_buckets_btwn_major_ticks to determine the number of major ticks. Defaults to 6. :type n_cbar_buckets: int, optional :param n_buckets_btwn_major_ticks: How many buckets are to lie between colorbar major ticks, determining how many major ticks are drawn. Defaults to 1. :type n_buckets_btwn_major_ticks: int, optional :param n_minor_ticks_btwn_major_ticks: How many minor ticks to draw between colorbar major ticks. Defaults to 8 (which means each pair of major ticks has 10 ticks total). :type n_minor_ticks_btwn_major_ticks: int, optional :param per_capita_denominator: When describing per-capita numbers, what to use as the denominator (e.g., cases per 100,000 people). If None, it is automatically computed per plot to be appropriately scaled for the data. :type per_capita_denominator: int, optional :raises ValueError: [description] :return: The two pieces of info required to make a Bokeh autoloading HTML+JS plot: the HTML div to be inserted somewhere in the HTML body, and the JS file that will load the plot into that div. :rtype: InfoForAutoload """ Counting.verify(count, allow_select=True) DiseaseStage.verify(stage, allow_select=True) # The date as a string, so that bokeh can use it as a column name STRING_DATE_COL = "String_Date_" # A column whose sole purpose is to be a (the same) date associated with each # location FAKE_DATE_COL = "Fake_Date_" # The column we'll actually use for the colors; it's computed from value_col COLOR_COL = "Color_" # Under no circumstances may you change this date format # It's not just a pretty date representation; it actually has to match up with the # date strings computed in JS DATE_FMT = r"%Y-%m-%d" ID_COLS = [ REGION_NAME_COL, Columns.DATE, Columns.STAGE, Columns.COUNT_TYPE, ] if cmap is None: cmap = cmocean.cm.matter if n_cbar_buckets is None: n_cbar_buckets = 6 if n_buckets_btwn_major_ticks is None: n_buckets_btwn_major_ticks = 1 if n_minor_ticks_btwn_major_ticks is None: n_minor_ticks_btwn_major_ticks = 8 n_cbar_major_ticks = n_cbar_buckets // n_buckets_btwn_major_ticks + 1 try: color_list = [ # Convert matplotlib colormap to bokeh (list of hex strings) # https://stackoverflow.com/a/49934218 RGB(*rgb).to_hex() for i, rgb in enumerate((255 * cmap(range(256))).astype("int")) ] except TypeError: color_list = cmap color_list: List[BokehColor] if stage is Select.ALL: stage_list = list(DiseaseStage) else: stage_list = [stage] if count is Select.ALL: count_list = list(Counting) else: count_list = [count] stage_list: List[DiseaseStage] count_list: List[Counting] stage_count_list: List[Tuple[DiseaseStage, Counting]] = list( itertools.product(stage_list, count_list)) df = df.copy() # Unadjust dates (see SaveFormats._adjust_dates) normalized_dates = df[Columns.DATE].dt.normalize() is_at_midnight = df[Columns.DATE] == normalized_dates df.loc[is_at_midnight, Columns.DATE] -= pd.Timedelta(days=1) df.loc[~is_at_midnight, Columns.DATE] = normalized_dates[~is_at_midnight] min_date, max_date = df[Columns.DATE].agg(["min", "max"]) dates: List[pd.Timestamp] = pd.date_range(start=min_date, end=max_date, freq="D") max_date_str = max_date.strftime(DATE_FMT) # Get day-by-day case diffs per location, date, stage, count-type # Make sure data exists for every date for every state so that the entire country is # plotted each day; fill missing data with 0 (missing really *is* as good as 0) # enums will be replaced by their name (kind of important) id_cols_product: pd.MultiIndex = pd.MultiIndex.from_product( [ df[REGION_NAME_COL].unique(), dates, [s.name for s in DiseaseStage], [c.name for c in Counting], ], names=ID_COLS, ) df = (id_cols_product.to_frame(index=False).merge( df, how="left", on=ID_COLS, ).sort_values(ID_COLS)) df[STRING_DATE_COL] = df[Columns.DATE].dt.strftime(DATE_FMT) df[Columns.CASE_COUNT] = df[Columns.CASE_COUNT].fillna(0) if transform_df_func is not None: df = transform_df_func(df) df = geo_df.merge(df, how="inner", on=REGION_NAME_COL)[[ REGION_NAME_COL, Columns.DATE, STRING_DATE_COL, Columns.STAGE, Columns.COUNT_TYPE, value_col, ]] dates: List[pd.Timestamp] = [ pd.Timestamp(d) for d in df[Columns.DATE].unique() ] values_mins_maxs = (df[df[value_col] > 0].groupby( [Columns.STAGE, Columns.COUNT_TYPE])[value_col].agg(["min", "max"])) vmins: pd.Series = values_mins_maxs["min"] vmaxs: pd.Series = values_mins_maxs["max"] pow10s_series: pd.Series = vmaxs.map( lambda x: int(10**(-np.floor(np.log10(x))))) # _pow_10s_series_dict = {} # for stage in DiseaseStage: # _pow_10s_series_dict.update( # { # (stage.name, Counting.TOTAL_CASES.name): 100000, # (stage.name, Counting.PER_CAPITA.name): 10000, # } # ) # pow10s_series = pd.Series(_pow_10s_series_dict) vmins: dict = vmins.to_dict() vmaxs: dict = vmaxs.to_dict() for stage in DiseaseStage: _value_key = (stage.name, Counting.PER_CAPITA.name) if per_capita_denominator is None: _max_pow10 = pow10s_series.loc[(slice(None), Counting.PER_CAPITA.name)].max() else: _max_pow10 = per_capita_denominator vmins[_value_key] *= _max_pow10 vmaxs[_value_key] *= _max_pow10 pow10s_series[_value_key] = _max_pow10 percap_pow10s: pd.Series = df.apply( lambda row: pow10s_series[ (row[Columns.STAGE], row[Columns.COUNT_TYPE])], axis=1, ) _per_cap_rows = df[Columns.COUNT_TYPE] == Counting.PER_CAPITA.name df.loc[_per_cap_rows, value_col] *= percap_pow10s.loc[_per_cap_rows] # Ideally we wouldn't have to pivot, and we could do a JIT join of state longs/lats # after filtering the data. Unfortunately this is not possible, and a long data # format leads to duplication of the very large long/lat lists; pivoting is how we # avoid that. (This seems to be one downside of bokeh when compared to plotly) df = (df.pivot_table( index=[REGION_NAME_COL, Columns.STAGE, Columns.COUNT_TYPE], columns=STRING_DATE_COL, values=value_col, aggfunc="first", ).reset_index().merge( geo_df[[REGION_NAME_COL, LONG_COL, LAT_COL]], how="inner", on=REGION_NAME_COL, )) # All three oclumns are just initial values; they'll change with the date slider df[value_col] = df[max_date_str] df[FAKE_DATE_COL] = max_date_str df[COLOR_COL] = np.where(df[value_col] > 0, df[value_col], "NaN") # Technically takes a df but we don't need the index bokeh_data_source = ColumnDataSource( {k: v.tolist() for k, v in df.to_dict(orient="series").items()}) filters = [[ GroupFilter(column_name=Columns.STAGE, group=stage.name), GroupFilter(column_name=Columns.COUNT_TYPE, group=count.name), ] for stage, count in stage_count_list] figures = [] for subplot_index, (stage, count) in enumerate(stage_count_list): # fig = bplotting.figure() # ax: plt.Axes = fig.add_subplot( # len(stage_list), len(count_list), subplot_index # ) # # Add timestamp to top right axis # if subplot_index == 2: # ax.text( # 1.25, # Coords are arbitrary magic numbers # 1.23, # f"Last updated {NOW_STR}", # horizontalalignment="right", # fontsize="small", # transform=ax.transAxes, # ) view = CDSView(source=bokeh_data_source, filters=filters[subplot_index]) vmin = vmins[(stage.name, count.name)] vmax = vmaxs[(stage.name, count.name)] # Compute and set axes titles if stage is DiseaseStage.CONFIRMED: fig_stage_name = "Cases" elif stage is DiseaseStage.DEATH: fig_stage_name = "Deaths" else: raise ValueError fig_title_components: List[str] = [] if subplot_title_prefix is not None: fig_title_components.append(subplot_title_prefix) fig_title_components.append(fig_stage_name) if count is Counting.PER_CAPITA: _per_cap_denom = pow10s_series[(stage.name, count.name)] fig_title_components.append(f"Per {_per_cap_denom:,d} people") formatter = PrintfTickFormatter(format=r"%2.3f") label_standoff = 12 tooltip_fmt = "{0.000}" else: formatter = NumeralTickFormatter(format="0.0a") label_standoff = 10 tooltip_fmt = "{0}" color_mapper = LogColorMapper( color_list, low=vmin, high=vmax, nan_color="#f2f2f2", ) fig_title = " ".join(fig_title_components) if plot_aspect_ratio is None: if x_range is None or y_range is None: raise ValueError("Must provide both `x_range` and `y_range`" + " when `plot_aspect_ratio` is None") plot_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] - y_range[0]) # Create figure object p = bplotting.figure( title=fig_title, title_location="above", tools=[ HoverTool( tooltips=[ ("Date", f"@{{{FAKE_DATE_COL}}}"), ("State", f"@{{{REGION_NAME_COL}}}"), ("Count", f"@{{{value_col}}}{tooltip_fmt}"), ], toggleable=False, ), PanTool(), BoxZoomTool(match_aspect=True), ZoomInTool(), ZoomOutTool(), ResetTool(), ], active_drag=None, aspect_ratio=plot_aspect_ratio, output_backend="webgl", lod_factor=4, lod_interval=400, lod_threshold=1000, lod_timeout=300, ) p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None # Finally, add the actual choropleth data we care about p.patches( LONG_COL, LAT_COL, source=bokeh_data_source, view=view, fill_color={ "field": COLOR_COL, "transform": color_mapper }, line_color="black", line_width=0.25, fill_alpha=1, ) # Add evenly spaced ticks and their labels to the colorbar # First major, then minor # Adapted from https://stackoverflow.com/a/50314773 bucket_size = (vmax / vmin)**(1 / n_cbar_buckets) tick_dist = bucket_size**n_buckets_btwn_major_ticks # Simple log scale math major_tick_locs = ( vmin * (tick_dist**np.arange(0, n_cbar_major_ticks)) # * (bucket_size ** 0.5) # Use this if centering ticks on buckets ) # Get minor locs by linearly interpolating between major ticks minor_tick_locs = [] for major_tick_index, this_major_tick in enumerate( major_tick_locs[:-1]): next_major_tick = major_tick_locs[major_tick_index + 1] # Get minor ticks as numbers in range [this_major_tick, next_major_tick] # and exclude the major ticks themselves (once we've used them to # compute the minor tick locs) minor_tick_locs.extend( np.linspace( this_major_tick, next_major_tick, n_minor_ticks_btwn_major_ticks + 2, )[1:-1]) color_bar = ColorBar( color_mapper=color_mapper, ticker=FixedTicker(ticks=major_tick_locs, minor_ticks=minor_tick_locs), formatter=formatter, label_standoff=label_standoff, major_tick_out=0, major_tick_in=13, major_tick_line_color="white", major_tick_line_width=1, minor_tick_out=0, minor_tick_in=5, minor_tick_line_color="white", minor_tick_line_width=1, location=(0, 0), border_line_color=None, bar_line_color=None, orientation="vertical", ) p.add_layout(color_bar, "right") p.hover.point_policy = "follow_mouse" # Bokeh axes (and most other things) are splattable p.axis.visible = False figures.append(p) # Make all figs pan and zoom together by setting their axes equal to each other # Also fix the plots' aspect ratios figs_iter = iter(np.ravel(figures)) anchor_fig = next(figs_iter) if x_range is not None and y_range is not None: data_aspect_ratio = (x_range[1] - x_range[0]) / (y_range[1] - y_range[0]) else: data_aspect_ratio = plot_aspect_ratio if x_range is not None: anchor_fig.x_range = Range1d( *x_range, bounds="auto", min_interval=min_visible_y_range * data_aspect_ratio, ) if y_range is not None: anchor_fig.y_range = Range1d(*y_range, bounds="auto", min_interval=min_visible_y_range) for fig in figs_iter: fig.x_range = anchor_fig.x_range fig.y_range = anchor_fig.y_range # 2x2 grid (for now) gp = gridplot( figures, ncols=len(count_list), sizing_mode="scale_both", toolbar_location="above", ) plot_layout = [gp] # Ok, pause # Now we're going into a whole other thing: we're doing all the JS logic behind a # date slider that changes which date is shown on the graphs. The structure of the # data is one column per date, one row per location, and a few extra columns to # store the data the graph will use. When we adjust the date of the slider, we copy # the relevant column of the df into the columns the graphs are looking at. # That's the easy part; the hard part is handling the "play button" functionality, # whereby the user can click one button and the date slider will periodically # advance itself. That requires a fair bit of logic to schedule and cancel the # timers and make it all feel right. # Create unique ID for the JS playback info object for this plot (since it'll be on # the webpage with other plots, and their playback info isn't shared) _THIS_PLOT_ID = uuid.uuid4().hex __TIMER = "'timer'" __IS_ACTIVE = "'isActive'" __SELECTED_INDEX = "'selectedIndex'" __BASE_INTERVAL_MS = "'BASE_INTERVAL'" # Time (in MS) btwn frames when speed==1 __TIMER_START_DATE = "'startDate'" __TIMER_ELAPSED_TIME_MS = "'elapsedTimeMS'" __TIMER_ELAPSED_TIME_PROPORTION = "'elapsedTimeProportion'" __SPEEDS_KEY = "'SPEEDS'" __PLAYBACK_INFO = f"window._playbackInfo_{_THIS_PLOT_ID}" _PBI_TIMER = f"{__PLAYBACK_INFO}[{__TIMER}]" _PBI_IS_ACTIVE = f"{__PLAYBACK_INFO}[{__IS_ACTIVE}]" _PBI_SELECTED_INDEX = f"{__PLAYBACK_INFO}[{__SELECTED_INDEX}]" _PBI_TIMER_START_DATE = f"{__PLAYBACK_INFO}[{__TIMER_START_DATE}]" _PBI_TIMER_ELAPSED_TIME_MS = f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_MS}]" _PBI_TIMER_ELAPSED_TIME_PROPORTION = ( f"{__PLAYBACK_INFO}[{__TIMER_ELAPSED_TIME_PROPORTION}]") _PBI_BASE_INTERVAL = f"{__PLAYBACK_INFO}[{__BASE_INTERVAL_MS}]" _PBI_SPEEDS = f"{__PLAYBACK_INFO}[{__SPEEDS_KEY}]" _PBI_CURR_INTERVAL_MS = ( f"{_PBI_BASE_INTERVAL} / {_PBI_SPEEDS}[{_PBI_SELECTED_INDEX}]") _SPEED_OPTIONS = [0.25, 0.5, 1.0, 2.0] _DEFAULT_SPEED = 1.0 _DEFAULT_SELECTED_INDEX = _SPEED_OPTIONS.index(_DEFAULT_SPEED) _SETUP_WINDOW_PLAYBACK_INFO = f""" if (typeof({__PLAYBACK_INFO}) === 'undefined') {{ {__PLAYBACK_INFO} = {{ {__TIMER}: null, {__IS_ACTIVE}: false, {__SELECTED_INDEX}: {_DEFAULT_SELECTED_INDEX}, {__TIMER_START_DATE}: null, {__TIMER_ELAPSED_TIME_MS}: 0, {__TIMER_ELAPSED_TIME_PROPORTION}: 0, {__BASE_INTERVAL_MS}: 1000, {__SPEEDS_KEY}: {_SPEED_OPTIONS} }}; }} """ _DEFFUN_INCR_DATE = f""" // See this link for why this works (it's an undocumented feature?) // https://discourse.bokeh.org/t/5254 // Tl;dr we need this to automatically update the hover as the play button plays // Without this, the hover tooltip only updates when we jiggle the mouse // slightly let prev_val = null; source.inspect.connect(v => prev_val = v); function updateDate() {{ {_PBI_TIMER_START_DATE} = new Date(); {_PBI_TIMER_ELAPSED_TIME_MS} = 0 if (dateSlider.value < maxDate) {{ dateSlider.value += 86400000; }} if (dateSlider.value >= maxDate) {{ console.log(dateSlider.value, maxDate) console.log('reached end') clearInterval({_PBI_TIMER}); {_PBI_IS_ACTIVE} = false; playPauseButton.active = false; playPauseButton.change.emit(); playPauseButton.label = 'Restart'; }} dateSlider.change.emit(); // This is pt. 2 of the prev_val/inspect stuff above if (prev_val !== null) {{ source.inspect.emit(prev_val); }} }} """ _DO_START_TIMER = f""" function startLoopTimer() {{ updateDate(); if ({_PBI_IS_ACTIVE}) {{ {_PBI_TIMER} = setInterval(updateDate, {_PBI_CURR_INTERVAL_MS}) }} }} {_PBI_TIMER_START_DATE} = new Date(); // Should never be <0 or >1 but I am being very defensive here const proportionRemaining = 1 - ( {_PBI_TIMER_ELAPSED_TIME_PROPORTION} <= 0 ? 0 : {_PBI_TIMER_ELAPSED_TIME_PROPORTION} >= 1 ? 1 : {_PBI_TIMER_ELAPSED_TIME_PROPORTION} ); const remainingTimeMS = ( {_PBI_CURR_INTERVAL_MS} * proportionRemaining ); const initialInterval = ( {_PBI_TIMER_ELAPSED_TIME_MS} === 0 ? 0 : remainingTimeMS ); {_PBI_TIMER} = setTimeout( startLoopTimer, initialInterval ); """ _DO_STOP_TIMER = f""" const now = new Date(); {_PBI_TIMER_ELAPSED_TIME_MS} += ( now.getTime() - {_PBI_TIMER_START_DATE}.getTime() ); {_PBI_TIMER_ELAPSED_TIME_PROPORTION} = ( {_PBI_TIMER_ELAPSED_TIME_MS} / {_PBI_CURR_INTERVAL_MS} ); clearInterval({_PBI_TIMER}); """ update_on_date_change_callback = CustomJS( args={"source": bokeh_data_source}, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} const sliderValue = cb_obj.value; const sliderDate = new Date(sliderValue) // Ugh, actually requiring the date to be YYYY-MM-DD (matching DATE_FMT) const dateStr = sliderDate.toISOString().split('T')[0] const data = source.data; {_PBI_TIMER_ELAPSED_TIME_MS} = 0 if (typeof(data[dateStr]) !== 'undefined') {{ data['{value_col}'] = data[dateStr] const valueCol = data['{value_col}']; const colorCol = data['{COLOR_COL}']; const fakeDateCol = data['{FAKE_DATE_COL}'] for (var i = 0; i < data['{value_col}'].length; i++) {{ const value = valueCol[i] if (value == 0) {{ colorCol[i] = 'NaN'; }} else {{ colorCol[i] = value; }} fakeDateCol[i] = dateStr; }} source.change.emit(); }} """, ) # Taking day-over-day diffs means the min slider day is one more than the min data # date (might be off by 1 if not using day over diffs but in practice not an issue) min_slider_date = min_date + pd.Timedelta(days=1) date_slider = DateSlider( start=min_slider_date, end=max_date, value=max_date, step=1, sizing_mode="stretch_width", width_policy="fit", ) date_slider.js_on_change("value", update_on_date_change_callback) play_pause_button = Toggle( label="Start playing", button_type="success", active=False, sizing_mode="stretch_width", ) animate_playback_callback = CustomJS( args={ "source": bokeh_data_source, "dateSlider": date_slider, "playPauseButton": play_pause_button, "maxDate": max_date, "minDate": min_slider_date, }, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} {_DEFFUN_INCR_DATE} if (dateSlider.value >= maxDate) {{ if (playPauseButton.active) {{ dateSlider.value = minDate; dateSlider.change.emit(); // Hack to get timer to wait after date slider wraps; any positive // number works but the smaller the better {_PBI_TIMER_ELAPSED_TIME_MS} = 1; }} }} const active = cb_obj.active; {_PBI_IS_ACTIVE} = active; if (active) {{ playPauseButton.label = 'Playing – Click/tap to pause' {_DO_START_TIMER} }} else {{ playPauseButton.label = 'Paused – Click/tap to play' {_DO_STOP_TIMER} }} """, ) play_pause_button.js_on_click(animate_playback_callback) change_playback_speed_callback = CustomJS( args={ "source": bokeh_data_source, "dateSlider": date_slider, "playPauseButton": play_pause_button, "maxDate": max_date, }, code=f""" {_SETUP_WINDOW_PLAYBACK_INFO} {_DEFFUN_INCR_DATE} // Must stop timer before handling changing the speed, as stopping the timer // saves values based on the current (unchaged) speed selection if ({_PBI_TIMER} !== null) {{ {_DO_STOP_TIMER} }} const selectedIndex = cb_obj.active; {_PBI_SELECTED_INDEX} = selectedIndex; if ({_PBI_IS_ACTIVE}) {{ {_DO_START_TIMER} }} else {{ {_PBI_TIMER_ELAPSED_TIME_MS} = 0 }} console.log({__PLAYBACK_INFO}) """, ) playback_speed_radio = RadioButtonGroup( labels=[f"{speed:.2g}x speed" for speed in _SPEED_OPTIONS], active=_DEFAULT_SELECTED_INDEX, sizing_mode="stretch_width", ) playback_speed_radio.js_on_click(change_playback_speed_callback) plot_layout.append( layout_column( [ date_slider, layout_row( [play_pause_button, playback_speed_radio], height_policy="min", ), ], width_policy="fit", height_policy="min", )) plot_layout = layout_column(plot_layout, sizing_mode="scale_both") # grid = gridplot(figures, ncols=len(count_list), sizing_mode="stretch_both") # Create the autoloading bokeh plot info (HTML + JS) js_path = str(Path(out_file_basename + "_autoload").with_suffix(".js")) tag_html_path = str( Path(out_file_basename + "_div_tag").with_suffix(".html")) js_code, tag_code = autoload_static(plot_layout, CDN, js_path) tag_uuid = re.search(r'id="([^"]+)"', tag_code).group(1) tag_code = re.sub(r'src="([^"]+)"', f'src="\\1?uuid={tag_uuid}"', tag_code) with open(Paths.DOCS / js_path, "w") as f_js, open(Paths.DOCS / tag_html_path, "w") as f_html: f_js.write(js_code) f_html.write(tag_code) # Create the video by creating stills of the graphs for each date and then stitching # the images into a video if should_make_video: save_dir: Path = PNG_SAVE_ROOT_DIR / out_file_basename save_dir.mkdir(parents=True, exist_ok=True) STILL_WIDTH = 1500 STILL_HEIGHT = int(np.ceil(STILL_WIDTH / plot_aspect_ratio) * 1.05) # Unclear why *1.05 is necessary gp.height = STILL_HEIGHT gp.width = STILL_WIDTH gp.sizing_mode = "fixed" orig_title = anchor_fig.title.text for date in dates: date_str = date.strftime(DATE_FMT) anchor_fig.title = Title(text=f"{orig_title} {date_str}") for p in figures: p.title = Title(text=p.title.text, text_font_size="20px") # Just a reimplementation of the JS code in the date slider's callback data = bokeh_data_source.data data[value_col] = data[date_str] for i, value in enumerate(data[value_col]): if value == 0: data[COLOR_COL][i] = "NaN" else: data[COLOR_COL][i] = value data[FAKE_DATE_COL][i] = date_str save_path: Path = (save_dir / date_str).with_suffix(".png") export_png(gp, filename=save_path) resize_to_even_dims(save_path, pad_bottom=0.08) if date == max(dates): poster_path: Path = ( PNG_SAVE_ROOT_DIR / (out_file_basename + "_poster")).with_suffix(".png") poster_path.write_bytes(save_path.read_bytes()) make_video(save_dir, out_file_basename, 0.9) print(f"Did interactive {out_file_basename}") return (js_code, tag_code)
from bokeh.io import show from bokeh.plotting import figure from bokeh.layouts import row from bokeh.models import HoverTool, ColumnDataSource, TapTool, ResetTool, BoxZoomTool from BokehStructureGraph.BokehStructureGraph import BokehStructureGraph import numpy as np x=np.linspace(-10,10,100) y=np.sin(x) signal=y+np.random.normal(0,.3,size=x.shape[0]) source=ColumnDataSource({'x':x,'y':y,'signal':signal}) # draw the structure graph of a basic figure model f=figure() f.scatter(x='x',y='signal',source=source, color='red', legend_label="measured") f.line(x='x',y='y',source=source, color='blue', legend_label="truth") f.xgrid.grid_line_color='white' f.ygrid.grid_line_color='white' f.background_fill_color="#eeeeee" f.title.text = "Figure being analyzed" f.legend.location = "top_left" f.legend.click_policy="hide" f.legend.background_fill_alpha = 0.0 T = HoverTool() T.renderers=[f.renderers[0]] S = TapTool() S.renderers=[f.renderers[0]] f.tools=[T,S, ResetTool(),BoxZoomTool()] show(row(f,BokehStructureGraph(f).model))
def fb_graph(): with open('facebook.json', 'r') as read_file: data = json.load(read_file) FB = nx.Graph(incoming_graph_data=data) degrees = FB.degree() nodes = FB.nodes() eigenv = nx.eigenvector_centrality(FB, weight=None) between = nx.betweenness_centrality(FB, weight=None) node_color = {k: v for k, v in FB.degree()} node_size = {k: .1 * v for k, v in FB.degree()} nx.set_node_attributes(FB, node_color, 'node_color') nx.set_node_attributes(FB, node_size, 'node_size') mapper = LinearColorMapper(palette=Viridis256, low=256, high=0) connections = list(node_color.values()) eigenv = list(eigenv.values()) between = list(between.values()) source = ColumnDataSource( pd.DataFrame.from_dict({k: v for k, v in FB.nodes(data=True)}, orient='index')) source.add(connections, "connections") source.add(eigenv, "eigenv") source.add(between, "between") boxcolor = BoxAnnotation(fill_color='white', line_color='black') plot_option = { 'background_fill_color': 'gray', 'background_fill_alpha': .5, 'tools': [ PanTool(), WheelZoomTool(), TapTool(), BoxZoomTool(overlay=boxcolor), BoxSelectTool(overlay=boxcolor), ResetTool() ], 'x_range': Range1d(-1, 1), 'y_range': Range1d(-1, 1), 'plot_width': 800, 'plot_height': 800, } plot = figure(**plot_option, x_axis_location=None, y_axis_location=None, output_backend="webgl") plot.title.text = "Facebook Graph" graph = from_networkx(FB, nx.spring_layout) graph.node_renderer.data_source = source graph.node_renderer.glyph = Circle(name="Node", size='node_size', fill_color={ 'field': 'node_color', 'transform': mapper }) graph.edge_renderer.glyph = MultiLine(line_width=.5) graph.edge_renderer.selection_glyph = MultiLine(line_color='white', line_width=1) plot.add_tools( HoverTool(name="Node", tooltips=[ ('Node', '$index'), ('Connections', '@connections'), ('Eigen Value', '@eigenv{.000000}'), ('Betweenness', '@between{.000000}'), ])) graph.selection_policy = NodesAndLinkedEdges() plot.toolbar.active_scroll = plot.select_one(WheelZoomTool) #4038 total nodes plot.renderers.append(graph) script, div = components(plot) cdn_js = CDN.js_files cdn_css = CDN.css_files return plot
def plot_clusters_bokeh(list_x, list_y, list_pais, k, etiquetas, grados_pertenencia, title='Title', to_save=True): ### Plotear con hover tool ### Colores a usar para cada cluster colores = ['blue', 'yellow', 'red', 'green', 'orange', 'purple'] ### Creo la herramienta de hover tool hover = HoverTool(tooltips=[ ("pais", "@pais"), ("index", "$index"), ("(x,y)", "(@x, @y)"), ("cluster_id", "@cluster_id"), ("Pertenencia_clusters", "@grados_p"), ]) ### Creo la figura p = figure( plot_width=700, plot_height=500, tools=[hover, PanTool(), ResetTool(), BoxZoomTool(), WheelZoomTool()], title=title) ### PLoteo cada cluster for i in range(k): source = ColumnDataSource( data={ 'x': list_x[np.where(etiquetas == i)], 'y': list_y[np.where(etiquetas == i)], 'pais': list_pais[np.where(etiquetas == i)], 'grados_p': grados_pertenencia[np.where(etiquetas == i)], 'cluster_id': etiquetas[np.where(etiquetas == i)] }) p.circle('x', 'y', size=12, fill_color=colores[i], source=source) ### Ploteo centroides #p.square(centroids_pca[:,0], centroids_pca[:,1], size=15, fill_color='black') ### Labels (componentes principales) p.xaxis.axis_label = 'Componente principal 1' p.yaxis.axis_label = 'Componente principal 2' #p.xaxis.axis_label = datos.columns[-2] #p.yaxis.axis_label = datos.columns[-1] if to_save: ### Guardo el resultado output_file('outputs/ClustersGenerados/cluster_inicial_' + title + '.html') save(p) return None
def remove_cells_by_hand(z, imgs, cell_poss, cell_sizes, cell_acts, size=(600, 600), vmax=None, remove_border=0): from bokeh.models import LassoSelectTool, BoxSelectTool, ResetTool, WheelZoomTool, BoxZoomTool from bokeh.plotting import figure, show, output_notebook from bokeh.models import CustomJS, ColumnDataSource, Circle, Toolbar, ToolbarPanel from bokeh.io import push_notebook, reset_output from bokeh.layouts import row output_notebook(hide_banner=True) img = imgs[z] if vmax != None: img = np.clip(img, 0, vmax) nx, ny = img.shape # delete border Cells border_idxs = np.asarray([ idx for idx, pos in enumerate(cell_poss[z]) if pos[0] <= remove_border or pos[0] >= nx - remove_border or pos[1] <= remove_border or pos[1] >= ny - remove_border ]) cell_poss[z] = np.delete(cell_poss[z], border_idxs, axis=0) cell_sizes[z] = np.delete(cell_sizes[z], border_idxs, axis=0) cell_acts[z] = np.delete(cell_acts[z], border_idxs, axis=0) cell_pos = cell_poss[z] cell_size = cell_sizes[z] cell_act = cell_acts[z] def save(b): global y print(y) if y == '': return rem_idxs = np.asarray( [idx for idx, y in enumerate(map(float, y.split(','))) if y < 0]) y = '' print(cell_poss[z].shape) cell_poss[z] = np.delete(cell_poss[z], rem_idxs, axis=0) cell_sizes[z] = np.delete(cell_sizes[z], rem_idxs, axis=0) cell_acts[z] = np.delete(cell_acts[z], rem_idxs, axis=0) print(cell_poss[z].shape) button = widgets.Button(description="Save", button_style='success', icon='save') button.on_click(save) display(button) source = ColumnDataSource(data=dict(x=[], y=[], s=[])) select_callback = CustomJS(args=dict(s=source), code=""" var idxs = s.selected.indices; if (idxs.length == 0) return; var d = s.data; for (var i = 0; i < idxs.length; i++) d['y'][idxs[i]] = -1000; s.change.emit(); cmd = "pf.y = '" + d['y'] + "'"; IPython.notebook.kernel.execute(cmd, {}, {}); """) lasso_tool = LassoSelectTool(callback=select_callback, select_every_mousemove=False) box_tool = BoxSelectTool(callback=select_callback, select_every_mousemove=False) wheel_tool = WheelZoomTool(zoom_on_axis=False) tools = [ResetTool(), BoxZoomTool(), lasso_tool, box_tool, wheel_tool] p = figure(title="draw to remove:", output_backend="webgl", toolbar_location="above", tools=tools, x_range=(0, nx), y_range=(ny, 0), plot_width=size[0], plot_height=size[1]) p.xgrid.visible = p.ygrid.visible = False p.toolbar_location = None p.add_layout(ToolbarPanel(toolbar=Toolbar(tools=tools)), "right") lasso_overlay = p.select_one(LassoSelectTool).overlay lasso_overlay.fill_color = "firebrick" lasso_overlay.line_color = "white" h = show(row(p), notebook_handle=True) p.image(image=[img[::-1]], x=0, y=ny, dw=nx, dh=ny, palette="Viridis256", level="image") scat = p.circle('x', 'y', color='red', size='s', source=source, fill_color='red', alpha=1) scat.data_source.data = dict(x=cell_pos[:, 0], y=cell_pos[:, 1], s=cell_size * 2) scat.nonselection_glyph = Circle(line_color='red', fill_color='red') push_notebook(handle=h)
def plot_cluster_bokeh_cambios(X_data_pca, list_x, list_y, list_xv, list_yv, k, cambios_variables, list_pais, grados_pertenencia, etiquetas, etiquetas_prev, centroids, centroids_viej, centroids_pca, title='Title', to_save=True): ### Hover tool para los datos hover = HoverTool(tooltips=[ ("pais", "@pais"), ("index", "$index"), ("(x,y)", "(@x, @y)"), ("(Cambio_x,Cambio_y)", "(@xv, @yv)"), ("cluster_id", "@cluster_id"), ("Pertenencia_clusters", "@grados_p"), ]) ### Colores a usar para cada cluster colores = ['blue', 'yellow', 'red', 'green', 'orange', 'purple'] ### Crear la figura p = figure( plot_width=700, plot_height=500, tools=[hover, PanTool(), ResetTool(), BoxZoomTool(), WheelZoomTool()], title=title) ### PLoteo cada conjunto for i in range(k): source = ColumnDataSource( data={ 'x': list_x[np.where(etiquetas == i)], 'y': list_y[np.where(etiquetas == i)], 'xv': list_xv[np.where(etiquetas == i)], 'yv': list_yv[np.where(etiquetas == i)], 'pais': list_pais[np.where(etiquetas == i)], 'grados_p': grados_pertenencia[np.where(etiquetas == i)], 'cluster_id': etiquetas[np.where(etiquetas == i)] }) p.circle('x', 'y', size=12, fill_color=colores[i], source=source) ### Veo cuales cambiaron de cluster etiquetas_cambios = np.where(etiquetas_prev - etiquetas != 0) etiqs = etiquetas[etiquetas_cambios] ### PLoteo los elementos de cada conjunto que cambiaron de cluster ### Listas para los elementos que cambiaron de cluster X_data_cambios = X_data_pca[etiquetas_cambios] list_x = X_data_cambios[:, 0] list_y = X_data_cambios[:, 1] list_xv = cambios_variables[:, 0] list_yv = cambios_variables[:, 1] list_pais = list_pais[etiquetas_cambios] grados_pertenencia = grados_pertenencia[etiquetas_cambios] ## Plotear elementos que cambiaron de cluster for i in range(k): source = ColumnDataSource( data={ 'x': list_x[np.where(etiqs == i)], 'y': list_y[np.where(etiqs == i)], 'xv': list_xv[np.where(etiqs == i)], 'yv': list_yv[np.where(etiqs == i)], 'pais': list_pais[np.where(etiqs == i)], 'grados_p': grados_pertenencia[np.where(etiqs == i)], 'cluster_id': etiqs[np.where(etiqs == i)] }) p.square('x', 'y', size=6, fill_color='white', source=source) ### Ploteo centroides ### Listas para centroiodes cambios_centroids = centroids_viej - centroids list_x = centroids_pca[:, 0] list_y = centroids_pca[:, 1] list_xv = cambios_centroids[:, 0] list_yv = cambios_centroids[:, 1] # Plotar centroids source = ColumnDataSource(data={ 'x': list_x, 'y': list_y, 'xv': list_xv, 'yv': list_yv }) # p.square('x','y', size=15, fill_color='black',source=source) ### Labels de la grafica (componentes principales) p.xaxis.axis_label = 'Componente principal 1' p.yaxis.axis_label = 'Componente principal 2' #p.xaxis.axis_label = datos.columns[-2] #p.yaxis.axis_label = datos.columns[-1] if to_save: ### Guardar resultados output_file('outputs/ClustersGenerados/cluster' + title + '.html') save(p) return None
def __init__(self, n_rectangles=1000, clear_interval=20000, **kwargs): """ kwargs are applied to the bokeh.models.plots.Plot constructor """ self.n_rectangles = n_rectangles self.clear_interval = clear_interval self.last = 0 self.source = ColumnDataSource( data=dict(start=[time() - clear_interval], duration=[0.1], key=['start'], name=['start'], color=['white'], duration_text=['100 ms'], worker=['foo'], y=[0], worker_thread=[1], alpha=[0.0])) x_range = DataRange1d(range_padding=0) y_range = DataRange1d(range_padding=0) self.root = figure(title="Task Stream", id='bk-task-stream-plot', x_range=x_range, y_range=y_range, toolbar_location="above", x_axis_type='datetime', min_border_right=35, tools='', **kwargs) self.root.yaxis.axis_label = 'Worker Core' rect = self.root.rect(source=self.source, x="start", y="y", width="duration", height=0.4, fill_color="color", line_color="color", line_alpha=0.6, fill_alpha="alpha", line_width=3) rect.nonselection_glyph = None self.root.yaxis.major_label_text_alpha = 0 self.root.yaxis.minor_tick_line_alpha = 0 self.root.xgrid.visible = False hover = HoverTool(point_policy="follow_mouse", tooltips=""" <div> <span style="font-size: 12px; font-weight: bold;">@name:</span> <span style="font-size: 10px; font-family: Monaco, monospace;">@duration_text</span> </div> """) tap = TapTool(callback=OpenURL(url='/profile?key=@name')) self.root.add_tools(hover, tap, BoxZoomTool(), ResetTool(reset_size=False), PanTool(dimensions="width"), WheelZoomTool(dimensions="width")) if ExportTool: export = ExportTool() export.register_plot(self.root) self.root.add_tools(export) # Required for update callback self.task_stream_index = [0]
mapper = CategoricalColorMapper(factors=factors, palette=colors) from bokeh.transform import jitter p.circle(x='scaled_suicides_no', y=jitter('year', width=0.6, range=p.y_range), source=source, alpha=0.3) p.x_range.range_padding = 0 p.ygrid.grid_line_color = None #p.circle('suicides_no', 'age', size=4, source=source, # legend='sex', fill_alpha=0.2, color = {"field":"sex","transform":mapper}) p.add_tools(PanTool()) p.add_tools(WheelZoomTool()) p.add_tools(BoxZoomTool()) p.add_tools(ResetTool()) show(p) #https://gist.github.com/dela3499/e159b388258b5f1a7a3bac42fc0179fd '''from bokeh.plotting import figure, output_file, show, ColumnDataSource from bokeh.models import HoverTool, CategoricalColorMapper from bokeh.io import output_notebook output_notebook() sourcef = ColumnDataSource( data=wh[(wh.country=='Japan')&(wh.sex=='female')] ) sourcem = ColumnDataSource( data=wh[(wh.country=='Japan')&(wh.sex=='male')] )
tools=TOOLS, plot_width=1000, toolbar_location="left", webgl=True) p.y_range = DataRange1d() p.xaxis.major_label_orientation = pi / 4 p.x_range.follow = "end" p.x_range.range_padding = 0 p.grid.grid_line_alpha = 0.3 p.min_border_left = 10 p.min_border_top = 10 p.add_tools(CrosshairTool(line_color='green', line_alpha=0.4)) p.add_tools(BoxZoomTool(dimensions=["width"])) p.add_tools(WheelZoomTool(dimensions=["width"])) #차트 데이터 모양 설정 width = 12 * 60 * 60 * 1000 # half day in ms p.segment(x0='date', y0='low', x1='date', y1='high', color="color", source=source) #p.segment(x0='date', y0='open', x1='date', y1='close',line_width=15, color="color", source=source) p.rect('date', 'mid', width,
def add_graph(self, field_names, legends, window='hann', window_length=256, noverlap=128): """ add a spectrogram plot to the graph field_names: can be a list of fields from the data set, or a list of functions with the data set as argument and returning a tuple of (field_name, data) legends: description for the field_names that will appear in the title of the plot window: the type of window to use for the frequency analysis. check scipy documentation for available window types. window_length: length of the analysis window in samples. noverlap: number of overlapping samples between windows. """ if self._had_error: return try: data_set = {} timestamp_key = 'timestamp' if 'timestamp_sample' in self._cur_dataset.data.keys(): timestamp_key = 'timestamp_sample' data_set[timestamp_key] = self._cur_dataset.data[timestamp_key] # calculate the sampling frequency # (Note: logging dropouts are not taken into account here) delta_t = ((data_set[timestamp_key][-1] - data_set[timestamp_key][0]) * 1.0e-6) / len(data_set[timestamp_key]) if delta_t < 0.000001: # avoid division by zero self._had_error = True return sampling_frequency = int(1.0 / delta_t) if sampling_frequency < 100: # require min sampling freq self._had_error = True return field_names_expanded = self._expand_field_names(field_names, data_set) # calculate the spectrogram psd = dict() for key in field_names_expanded: frequency, time, psd[key] = scipy.signal.spectrogram( data_set[key], fs=sampling_frequency, window=window, nperseg=window_length, noverlap=noverlap, scaling='density') # sum all psd's key_it = iter(psd) sum_psd = psd[next(key_it)] for key in key_it: sum_psd += psd[key] # offset = int(((1024/2.0)/250.0)*1e6) # scale time to microseconds and add start time as offset time = time * 1.0e6 + self._cur_dataset.data[timestamp_key][0] image = [10 * np.log10(sum_psd)] title = self.title for legend in legends: title += " " + legend title += " [dB]" # assume maximal data points per pixel at full resolution max_num_data_points = 2.0*self._config['plot_width'] if len(time) > max_num_data_points: step_size = int(len(time) / max_num_data_points) time = time[::step_size] image[0] = image[0][:, ::step_size] color_mapper = LinearColorMapper(palette="Viridis256", low=np.amin(image), high=np.amax(image)) self._p.y_range = Range1d(frequency[0], frequency[-1]) self._p.toolbar_location = 'above' self._p.image(image=image, x=time[0], y=frequency[0], dw=(time[-1]-time[0]), dh=(frequency[-1]-frequency[0]), color_mapper=color_mapper) color_bar = ColorBar(color_mapper=color_mapper, major_label_text_font_size="5pt", ticker=BasicTicker(desired_num_ticks=5), formatter=PrintfTickFormatter(format="%f"), title='[dB]', label_standoff=6, border_line_color=None, location=(0, 0)) self._p.add_layout(color_bar, 'right') # add plot zoom tool that only zooms in time axis wheel_zoom = WheelZoomTool() self._p.toolbar.tools = [PanTool(), wheel_zoom, BoxZoomTool(), ResetTool(), SaveTool()] # updated_tools self._p.toolbar.active_scroll = wheel_zoom except (KeyError, IndexError, ValueError, ZeroDivisionError) as error: print(type(error), "(" + self._data_name + "):", error) self._had_error = True
Simulation_End)) print('PercentError is {}'.format(PercentError)) #%%--------------------------MODEL COMPARISON----------------------------------------- #This code is only run when comparing the model results to field measurements. It is typically used for model validation if Compare_To_MeasuredData == 1: #Calculate the electricity consumed over the simulation and the % error for validation purposes Model['Total Electricity Consumption (kWh)'] = Model[ 'Electricity Consumed (kWh)'].cumsum() Model['Cumulative Percent Error (%)'] = ( Model['Total Electricity Consumption (kWh)'] - Model['Power_EnergySum_kWh']) / (Model['Power_EnergySum_kWh'] + 0.000000000001) * 100 tools = [LassoSelectTool(), WheelZoomTool(), BoxZoomTool(), ResetTool()] #The rest of the code creates and saves plots comparing the HPWH model to the monitored data. Detailed comments will not be provided. p1 = figure(width=1200, height=600, x_axis_label='Time (hr)', y_axis_label='Electric Power (W)', tools=tools) p1.title.text_font_size = '12pt' p1.line(x=Model['Time (hr)'], y=Model['Electric Power (W)'], legend='Model', color='red') p1.circle(x=Model['Time (hr)'], y=Model['Power_PowerSum_W'], legend='Data',
relevantWords, relevant_tweet = get_tweet_word2vec(default_search_1, tweet_dataset) rel_tweet = relevant_tweet.copy(deep=True) dates, counts = get_trend(relevant_tweet) ########### Create Visualizations ################## # Line graph for trend plot_trend = figure(title='Trend of Tweets', plot_width=600, plot_height=200, x_axis_type="datetime", tools=[HoverTool(), ResetTool(), BoxZoomTool()]) line1 = plot_trend.line(x=dates, y=counts, line_width=2) line1_datasource = line1.data_source plot_trend.xaxis.axis_label = "Date" plot_trend.yaxis.axis_label = "Relevant Tweet Count" # Widgets - Search, button button_go = Button(label="Search Revelant Tweets", button_type="success") button_go.on_click(bt_compare_click) rad_text = PreText(text="Choose Word Embedding :") radio_group = RadioGroup(labels=["WordNet Synset", "Word2Vec"], inline=True, active=1) user_text = PreText(text="")
ealpha=ealpha)) # plot ################ # Start timer t0 = time.time() ################ x_range = Range1d(0, 1) y_range = Range1d(0, 1) plot = Plot(x_range=x_range, y_range=y_range, plot_width=plot_width, plot_height=plot_height, title=title) plot.add_tools(PanTool(), WheelZoomTool(), BoxZoomTool(), PreviewSaveTool(), ResetTool()) # edges seg = Segment(x0="ex0", y0="ey0", x1="ex1", y1="ey1", \ line_width="ewidth",) seg_glyph = plot.add_glyph(esource, seg) # circles circ = Circle(x="x", y="y", size='size', line_color=None) circ_glyph = plot.add_glyph(nsource, circ) ################ # End Timer print(bokeh.__version__) print("Time to plot with Bokeh:", time.time() - t0, "seconds")
def plot_image(filename, save=False): hdu = fits.open(filename) dheader = dict(hdu[0].header) for k in dheader: if len(k) >= 2: print(f"{k}: {dheader[k]}") print(hdu.info()) data = hdu[0].data # quick hot pixel/ cosmic ray mask mask, cdata = detect_cosmics( data, psfmodel='gauss', psffwhm=4, psfsize=2*round(4)+1, # just a guess sepmed=False, sigclip = 4.25, niter=3, objlim=10, cleantype='idw', verbose=False ) # show how many pixels are saturated SATURATION = 2**(hdu[0].header['bitpix']) mmask = cdata >= SATURATION*0.9 labels, ngroups = label(mmask) print('Saturated Areas:',ngroups) labeli, counts = np.unique(labels, return_counts=True) bad_pix = {'x':[], 'y':[], 'value':[]} # loop through each group to find position for i in range(1,labeli[-1]+1): imask = labels == i yc,xc = np.argwhere(imask).mean(0) bad_pix['x'].append(xc) bad_pix['y'].append(yc) bad_pix['value'].append(cdata[imask].mean()) pprint(bad_pix) # create a figure with text on mouse hover\ print("Saturated pixels are marked with red. These are pixels which have exceeded the maximum value for brightness, and are thus not suitable for use as comparison stars.") fig = figure(tooltips=[("x", "$x"), ("y", "$y"), ("value", "@image")], plot_width=800, plot_height=800, tools=[PanTool(),BoxZoomTool(),WheelZoomTool(),ResetTool(),HoverTool()]) fig.x_range.range_padding = fig.y_range.range_padding = 0 r = fig.multi_line('x', 'y', source={'x':[],'y':[]},color='white',line_width=3) fig.add_tools(FreehandDrawTool(renderers=[r])) ##TODO: add colorbar # set up a colobar + data range color_mapper = LogColorMapper(palette="Cividis256", low=np.percentile(data, 55), high=np.percentile(data, 99)) # must give a vector of image data for image parameter fig.image( image=[cdata], x=0, y=0, dw=hdu[0].data.shape[1], dh=hdu[0].data.shape[0], level="image", color_mapper=color_mapper ) # plot saturated stars fig.x(bad_pix['x'], bad_pix['y'], size=25, color='red', line_width=3) fig.x(bad_pix['x'], bad_pix['y'], size=25, color='white', line_width=1) # TODO figure out hover value fig.grid.grid_line_width = 0.5 color_bar = ColorBar(color_mapper=color_mapper, ticker=LogTicker(), label_standoff=12, border_line_color=None, location=(0,0)) fig.add_layout(color_bar, 'right') if save: output_file("interactivefits.html") else: show(fig)
def large_plot(n: int) -> Tuple[Model, Set[Model]]: from bokeh.models import ( BoxSelectTool, BoxZoomTool, Column, ColumnDataSource, DataRange1d, GlyphRenderer, Grid, Line, LinearAxis, PanTool, Plot, ResetTool, SaveTool, WheelZoomTool, ZoomInTool, ZoomOutTool, ) col = Column() objects: Set[Model] = {col} for i in range(n): source = ColumnDataSource(data=dict(x=[0, i + 1], y=[0, i + 1])) xdr = DataRange1d() ydr = DataRange1d() plot = Plot(x_range=xdr, y_range=ydr) xaxis = LinearAxis() plot.add_layout(xaxis, "below") yaxis = LinearAxis() plot.add_layout(yaxis, "left") xgrid = Grid(dimension=0) plot.add_layout(xgrid, "center") ygrid = Grid(dimension=1) plot.add_layout(ygrid, "center") tickers = [ xaxis.ticker, xaxis.formatter, yaxis.ticker, yaxis.formatter ] glyph = Line(x='x', y='y') renderer = GlyphRenderer(data_source=source, glyph=glyph) plot.renderers.append(renderer) pan = PanTool() zoom_in = ZoomInTool() zoom_out = ZoomOutTool() wheel_zoom = WheelZoomTool() box_zoom = BoxZoomTool() box_select = BoxSelectTool() save = SaveTool() reset = ResetTool() tools = [ pan, zoom_in, zoom_out, wheel_zoom, box_zoom, box_select, save, reset ] plot.add_tools(*tools) col.children.append(plot) objects |= set([ xdr, ydr, xaxis, xaxis.major_label_policy, yaxis, yaxis.major_label_policy, xgrid, ygrid, renderer, renderer.view, glyph, source, source.selected, source.selection_policy, plot, plot.x_scale, plot.y_scale, plot.toolbar, plot.title, box_zoom.overlay, box_select.overlay, ] + tickers + tools) return col, objects
def mn_display(edges_df, analysis_id): # create networkx object G = nx.from_pandas_edgelist(edges_df, source='cluster1', target='cluster2', edge_attr=True) # convert node attributes node_dict = dict() node_dict['index'] = list(G.nodes()) node_attr_keys = [ attr_key for node in list(G.nodes(data=True)) for attr_key in node[1].keys() ] node_attr_keys = list(set(node_attr_keys)) for attr_key in node_attr_keys: node_dict[attr_key] = [ node_attr[attr_key] if attr_key in node_attr.keys() else None for node_key, node_attr in list(G.nodes(data=True)) ] # convert edge attributes edge_dict = dict() edge_dict['start'] = [x[0] for x in G.edges(data=True)] edge_dict['end'] = [x[1] for x in G.edges(data=True)] edge_attr_keys = [ attr_key for edge in list(G.edges(data=True)) for attr_key in edge[2].keys() ] edge_attr_keys = list(set(edge_attr_keys)) for attr_key in edge_attr_keys: edge_dict[attr_key] = [ edge_attr[attr_key] if attr_key in edge_attr.keys() else None for _, _, edge_attr in list(G.edges(data=True)) ] node_source = ColumnDataSource(data=node_dict) edge_source = ColumnDataSource(data=edge_dict) TOOLTIPS = [("cluster1", "@start"), ("score", "@similarity_score"), ("cluster2", "@end")] plot = Plot(x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1)) plot.add_tools(HoverTool(tooltips=TOOLTIPS), WheelZoomTool(), TapTool(), BoxSelectTool(), ResetTool(), PanTool(), BoxZoomTool()) # networkx object >> bokeh graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0)) graph_renderer.node_renderer.data_source.data = node_source.data graph_renderer.edge_renderer.data_source.data = edge_source.data # glyphs for nodes graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral6[0]) graph_renderer.node_renderer.selection_glyph = Circle( size=15, fill_color=Spectral6[2]) graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral6[1]) # glyphs for edges graph_renderer.edge_renderer.glyph = MultiLine(line_color="#cccccc", line_alpha=0.8, line_width=3) graph_renderer.edge_renderer.selection_glyph = MultiLine( line_color=Spectral6[2], line_width=3) graph_renderer.edge_renderer.hover_glyph = MultiLine( line_color=Spectral6[1], line_width=5) graph_renderer.selection_policy = NodesAndLinkedEdges() graph_renderer.inspection_policy = EdgesAndLinkedNodes() plot.renderers.append(graph_renderer) output_file("output.html", title="Analysis {}".format(analysis_id)) show(plot) resources = INLINE.render() script, div = components(plot) return plot, script, div, resources
def plot_scores(scores, legend=['Legend1', 'Legend2'], colors=['blue', 'green'], plot_title='Title', plot_xlabel='X', plot_ylabel='Y', plot_lib='matplotlib', matplotlib_style='default', fig_size=(800, 600), bokeh_notebook=False): if len(legend) == 0: for i in range(scores.shape[0]): legend.append('Legend ' + str(i)) if len(colors) == 0: colors = [ 'blue', 'green', 'red', 'mediumvioletred', 'magenta', 'sienna', 'maroon', 'brown' ] if plot_lib == 'matplotlib': plt.style.use(matplotlib_style) plt.grid() for i in range(scores.shape[0]): plt.plot(np.arange(len(scores[i, :])), scores[i, :], '-', color=colors[i], label=legend[i]) plt.title(plot_title) plt.xlabel(plot_xlabel) plt.ylabel(plot_ylabel) plt.legend(loc='best') plt.axis('tight') plt.show() elif plot_lib == 'seaborn': fig = plt.figure(figsize=fig_size) plt.grid() sns_plot = sns.tsplot(data=scores, err_style=['ci_band', 'ci_bars'], marker='o', legend=True) sns_plot.set(xlabel=plot_xlabel, ylabel=plot_ylabel) sns.plt.title(plot_title) if bokeh_notebook is True: output_notebook() show(mpl.to_bokeh(fig)) elif plot_lib == 'bokeh': if bokeh_notebook is True: output_notebook() num_exp = scores.shape[1] num_splits = scores.shape[0] hover = HoverTool(tooltips=[ ("(x, accuracy)", "($x, $y)"), ]) p = figure(title=plot_title, width=fig_size[1], height=fig_size[0], tools=[ hover, PanTool(), BoxZoomTool(), ResetTool(), WheelZoomTool(), SaveTool() ]) p.xaxis.axis_label = plot_xlabel p.yaxis.axis_label = plot_ylabel p.title.text = plot_title p.background_fill = 'beige' for i in range(num_exp): source = ColumnDataSource(data=dict( x=np.arange(num_splits), y=scores[:, i], )) p.circle(np.arange(num_splits), scores[i, :], color=colors[i], size=5, line_alpha=0, source=source) p.line(np.arange(num_splits), scores[i, :], color=colors[i], legend=legend[i], source=source) show(p)
def create(palm): doc = curdoc() # Calibration averaged waveforms per photon energy waveform_plot = Plot( title=Title(text="eTOF calibration waveforms"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=760, plot_width=PLOT_CANVAS_WIDTH, toolbar_location='right', ) # ---- tools waveform_plot.toolbar.logo = None waveform_plot_hovertool = HoverTool( tooltips=[("energy, eV", '@en'), ("eTOF bin", '$x{0.}')]) waveform_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool(), waveform_plot_hovertool) # ---- axes waveform_plot.add_layout(LinearAxis(axis_label='eTOF time bin'), place='below') waveform_plot.add_layout(LinearAxis(axis_label='Intensity', major_label_orientation='vertical'), place='left') # ---- grid lines waveform_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) waveform_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- multiline glyphs waveform_ref_source = ColumnDataSource(dict(xs=[], ys=[], en=[])) waveform_ref_multiline = waveform_plot.add_glyph( waveform_ref_source, MultiLine(xs='xs', ys='ys', line_color='blue')) waveform_str_source = ColumnDataSource(dict(xs=[], ys=[], en=[])) waveform_str_multiline = waveform_plot.add_glyph( waveform_str_source, MultiLine(xs='xs', ys='ys', line_color='red')) # ---- legend waveform_plot.add_layout( Legend(items=[( "reference", [waveform_ref_multiline]), ("streaked", [waveform_str_multiline])])) waveform_plot.legend.click_policy = "hide" # ---- vertical spans photon_peak_ref_span = Span(location=0, dimension='height', line_dash='dashed', line_color='blue') photon_peak_str_span = Span(location=0, dimension='height', line_dash='dashed', line_color='red') waveform_plot.add_layout(photon_peak_ref_span) waveform_plot.add_layout(photon_peak_str_span) # Calibration fit plot fit_plot = Plot( title=Title(text="eTOF calibration fit"), x_range=DataRange1d(), y_range=DataRange1d(), plot_height=PLOT_CANVAS_HEIGHT, plot_width=PLOT_CANVAS_WIDTH, toolbar_location='right', ) # ---- tools fit_plot.toolbar.logo = None fit_plot.add_tools(PanTool(), BoxZoomTool(), WheelZoomTool(), ResetTool()) # ---- axes fit_plot.add_layout(LinearAxis(axis_label='Photoelectron peak shift'), place='below') fit_plot.add_layout(LinearAxis(axis_label='Photon energy, eV', major_label_orientation='vertical'), place='left') # ---- grid lines fit_plot.add_layout(Grid(dimension=0, ticker=BasicTicker())) fit_plot.add_layout(Grid(dimension=1, ticker=BasicTicker())) # ---- circle glyphs fit_ref_circle_source = ColumnDataSource(dict(x=[], y=[])) fit_ref_circle = fit_plot.add_glyph( fit_ref_circle_source, Circle(x='x', y='y', line_color='blue')) fit_str_circle_source = ColumnDataSource(dict(x=[], y=[])) fit_str_circle = fit_plot.add_glyph(fit_str_circle_source, Circle(x='x', y='y', line_color='red')) # ---- line glyphs fit_ref_line_source = ColumnDataSource(dict(x=[], y=[])) fit_ref_line = fit_plot.add_glyph(fit_ref_line_source, Line(x='x', y='y', line_color='blue')) fit_str_line_source = ColumnDataSource(dict(x=[], y=[])) fit_str_line = fit_plot.add_glyph(fit_str_line_source, Line(x='x', y='y', line_color='red')) # ---- legend fit_plot.add_layout( Legend(items=[ ("reference", [fit_ref_circle, fit_ref_line]), ("streaked", [fit_str_circle, fit_str_line]), ])) fit_plot.legend.click_policy = "hide" # Calibration results datatables def datatable_ref_source_callback(_attr, _old, new): for en, ps, use in zip(new['energy'], new['peak_pos_ref'], new['use_in_fit']): palm.etofs['0'].calib_data.loc[ en, 'calib_tpeak'] = ps if ps != 'NaN' else np.nan palm.etofs['0'].calib_data.loc[en, 'use_in_fit'] = use calib_res = {} for etof_key in palm.etofs: calib_res[etof_key] = palm.etofs[etof_key].fit_calibration_curve() update_calibration_plot(calib_res) datatable_ref_source = ColumnDataSource( dict(energy=['', '', ''], peak_pos_ref=['', '', ''], use_in_fit=[True, True, True])) datatable_ref_source.on_change('data', datatable_ref_source_callback) datatable_ref = DataTable( source=datatable_ref_source, columns=[ TableColumn(field='energy', title="Photon Energy, eV", editor=IntEditor()), TableColumn(field='peak_pos_ref', title="Reference Peak", editor=IntEditor()), TableColumn(field='use_in_fit', title=" ", editor=CheckboxEditor(), width=80), ], index_position=None, editable=True, height=300, width=250, ) def datatable_str_source_callback(_attr, _old, new): for en, ps, use in zip(new['energy'], new['peak_pos_str'], new['use_in_fit']): palm.etofs['1'].calib_data.loc[ en, 'calib_tpeak'] = ps if ps != 'NaN' else np.nan palm.etofs['1'].calib_data.loc[en, 'use_in_fit'] = use calib_res = {} for etof_key in palm.etofs: calib_res[etof_key] = palm.etofs[etof_key].fit_calibration_curve() update_calibration_plot(calib_res) datatable_str_source = ColumnDataSource( dict(energy=['', '', ''], peak_pos_str=['', '', ''], use_in_fit=[True, True, True])) datatable_str_source.on_change('data', datatable_str_source_callback) datatable_str = DataTable( source=datatable_str_source, columns=[ TableColumn(field='energy', title="Photon Energy, eV", editor=IntEditor()), TableColumn(field='peak_pos_str', title="Streaked Peak", editor=IntEditor()), TableColumn(field='use_in_fit', title=" ", editor=CheckboxEditor(), width=80), ], index_position=None, editable=True, height=350, width=250, ) # eTOF calibration folder path text input def path_textinput_callback(_attr, _old, _new): path_periodic_update() update_load_dropdown_menu() path_textinput = TextInput(title="eTOF calibration path:", value=os.path.join(os.path.expanduser('~')), width=510) path_textinput.on_change('value', path_textinput_callback) # eTOF calibration eco scans dropdown def scans_dropdown_callback(_attr, _old, new): scans_dropdown.label = new scans_dropdown = Dropdown(label="ECO scans", button_type='default', menu=[]) scans_dropdown.on_change('value', scans_dropdown_callback) # ---- etof scans periodic update def path_periodic_update(): new_menu = [] if os.path.isdir(path_textinput.value): for entry in os.scandir(path_textinput.value): if entry.is_file() and entry.name.endswith('.json'): new_menu.append((entry.name, entry.name)) scans_dropdown.menu = sorted(new_menu, reverse=True) doc.add_periodic_callback(path_periodic_update, 5000) # Calibrate button def calibrate_button_callback(): try: palm.calibrate_etof_eco(eco_scan_filename=os.path.join( path_textinput.value, scans_dropdown.value)) except Exception: palm.calibrate_etof(folder_name=path_textinput.value) datatable_ref_source.data.update( energy=palm.etofs['0'].calib_data.index.tolist(), peak_pos_ref=palm.etofs['0'].calib_data['calib_tpeak'].tolist(), use_in_fit=palm.etofs['0'].calib_data['use_in_fit'].tolist(), ) datatable_str_source.data.update( energy=palm.etofs['0'].calib_data.index.tolist(), peak_pos_str=palm.etofs['1'].calib_data['calib_tpeak'].tolist(), use_in_fit=palm.etofs['1'].calib_data['use_in_fit'].tolist(), ) def update_calibration_plot(calib_res): etof_ref = palm.etofs['0'] etof_str = palm.etofs['1'] shift_val = 0 etof_ref_wf_shifted = [] etof_str_wf_shifted = [] for wf_ref, wf_str in zip(etof_ref.calib_data['waveform'], etof_str.calib_data['waveform']): shift_val -= max(wf_ref.max(), wf_str.max()) etof_ref_wf_shifted.append(wf_ref + shift_val) etof_str_wf_shifted.append(wf_str + shift_val) waveform_ref_source.data.update( xs=len(etof_ref.calib_data) * [list(range(etof_ref.internal_time_bins))], ys=etof_ref_wf_shifted, en=etof_ref.calib_data.index.tolist(), ) waveform_str_source.data.update( xs=len(etof_str.calib_data) * [list(range(etof_str.internal_time_bins))], ys=etof_str_wf_shifted, en=etof_str.calib_data.index.tolist(), ) photon_peak_ref_span.location = etof_ref.calib_t0 photon_peak_str_span.location = etof_str.calib_t0 def plot_fit(time, calib_a, calib_b): time_fit = np.linspace(np.nanmin(time), np.nanmax(time), 100) en_fit = (calib_a / time_fit)**2 + calib_b return time_fit, en_fit def update_plot(calib_results, circle, line): (a, c), x, y = calib_results x_fit, y_fit = plot_fit(x, a, c) circle.data.update(x=x, y=y) line.data.update(x=x_fit, y=y_fit) update_plot(calib_res['0'], fit_ref_circle_source, fit_ref_line_source) update_plot(calib_res['1'], fit_str_circle_source, fit_str_line_source) calib_const_div.text = """ a_str = {:.2f}<br> b_str = {:.2f}<br> <br> a_ref = {:.2f}<br> b_ref = {:.2f} """.format(etof_str.calib_a, etof_str.calib_b, etof_ref.calib_a, etof_ref.calib_b) calibrate_button = Button(label="Calibrate eTOF", button_type='default', width=250) calibrate_button.on_click(calibrate_button_callback) # Photon peak noise threshold value text input def phot_peak_noise_thr_textinput_callback(_attr, old, new): try: new_value = float(new) if new_value > 0: for etof in palm.etofs.values(): etof.photon_peak_noise_thr = new_value else: phot_peak_noise_thr_textinput.value = old except ValueError: phot_peak_noise_thr_textinput.value = old phot_peak_noise_thr_textinput = TextInput( title='Photon peak noise threshold:', value=str(1)) phot_peak_noise_thr_textinput.on_change( 'value', phot_peak_noise_thr_textinput_callback) # Electron peak noise threshold value text input def el_peak_noise_thr_textinput_callback(_attr, old, new): try: new_value = float(new) if new_value > 0: for etof in palm.etofs.values(): etof.electron_peak_noise_thr = new_value else: el_peak_noise_thr_textinput.value = old except ValueError: el_peak_noise_thr_textinput.value = old el_peak_noise_thr_textinput = TextInput( title='Electron peak noise threshold:', value=str(10)) el_peak_noise_thr_textinput.on_change( 'value', el_peak_noise_thr_textinput_callback) # Save calibration button def save_button_callback(): palm.save_etof_calib(path=path_textinput.value) update_load_dropdown_menu() save_button = Button(label="Save", button_type='default', width=250) save_button.on_click(save_button_callback) # Load calibration button def load_dropdown_callback(_attr, _old, new): if new: palm.load_etof_calib(os.path.join(path_textinput.value, new)) datatable_ref_source.data.update( energy=palm.etofs['0'].calib_data.index.tolist(), peak_pos_ref=palm.etofs['0'].calib_data['calib_tpeak'].tolist( ), use_in_fit=palm.etofs['0'].calib_data['use_in_fit'].tolist(), ) datatable_str_source.data.update( energy=palm.etofs['0'].calib_data.index.tolist(), peak_pos_str=palm.etofs['1'].calib_data['calib_tpeak'].tolist( ), use_in_fit=palm.etofs['1'].calib_data['use_in_fit'].tolist(), ) # Drop selection, so that this callback can be triggered again on the same dropdown menu # item from the user perspective load_dropdown.value = '' def update_load_dropdown_menu(): new_menu = [] calib_file_ext = '.palm_etof' if os.path.isdir(path_textinput.value): for entry in os.scandir(path_textinput.value): if entry.is_file() and entry.name.endswith((calib_file_ext)): new_menu.append( (entry.name[:-len(calib_file_ext)], entry.name)) load_dropdown.button_type = 'default' load_dropdown.menu = sorted(new_menu, reverse=True) else: load_dropdown.button_type = 'danger' load_dropdown.menu = new_menu doc.add_next_tick_callback(update_load_dropdown_menu) doc.add_periodic_callback(update_load_dropdown_menu, 5000) load_dropdown = Dropdown(label="Load", menu=[], width=250) load_dropdown.on_change('value', load_dropdown_callback) # eTOF fitting equation fit_eq_div = Div( text="""Fitting equation:<br><br><img src="/palm/static/5euwuy.gif">""" ) # Calibration constants calib_const_div = Div(text=""" a_str = {}<br> b_str = {}<br> <br> a_ref = {}<br> b_ref = {} """.format(0, 0, 0, 0)) # assemble tab_layout = column( row( column(waveform_plot, fit_plot), Spacer(width=30), column( path_textinput, scans_dropdown, calibrate_button, phot_peak_noise_thr_textinput, el_peak_noise_thr_textinput, row(save_button, load_dropdown), row(datatable_ref, datatable_str), calib_const_div, fit_eq_div, ), )) return Panel(child=tab_layout, title="eTOF Calibration")