def modify_doc(doc): plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) slider = DateSlider(start=start, end=end, value=value, css_classes=["foo"], width=300) def cb(attr, old, new): slider.title = "baz" slider.on_change('value', cb) doc.add_root(column(slider, plot))
def modify_doc(doc): plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) slider = DateSlider(start=start, end=end, value=value, css_classes=["foo"], width=300, bar_color="red") def cb(attr, old, new): slider.bar_color = "rgba(255, 255, 0, 1)" slider.on_change('value', cb) doc.add_root(column(slider, plot))
def modify_doc(doc): plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) slider = DateSlider(start=start, end=end, value=value, css_classes=["foo"], width=300) def cbv(attr, old, new): junk['v'] += 1 def cbvt(attr, old, new): junk['vt'] += 1 slider.on_change('value', cbv) slider.on_change('value_throttled', cbvt) doc.add_root(column(slider, plot))
def modify_doc(doc): source = ColumnDataSource(dict(x=[1, 2], y=[1, 1], val=["a", "b"])) plot = Plot(height=400, width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0) plot.add_glyph(source, Circle(x='x', y='y', size=20)) plot.add_tools(CustomAction(callback=CustomJS(args=dict(s=source), code=RECORD("data", "s.data")))) slider = DateSlider(start=start, end=end, value=value, css_classes=["foo"], width=300, step=24*3600*1000) def cb(attr, old, new): source.data['val'] = [slider.value_as_date.isoformat()] slider.on_change('value', cb) doc.add_root(column(slider, plot))
fill_color={ 'field': 'temp_anomalies', 'transform': color_mapper }, fill_alpha=0.5, line_color=None) color_bar = ColorBar(color_mapper=color_mapper, label_standoff=12, border_line_color=None, location=(0, 0)) p.add_layout(color_bar, 'right') # Make a slider object: slider slider = DateSlider(title="Date", value=min(climate_data.index), start=min(climate_data.index), end=max(climate_data.index), step=1, width=900) # Attach the callback to the 'value' property of slider slider.on_change('value', update_plot) # Make a row layout of widgetbox(slider) and plot and add it to the current document layout = column(widgetbox(slider, sizing_mode='scale_width'), p) # Add the plot to the current document and add a title curdoc().add_root(layout) curdoc().title = 'Global Warming'
max(df_map[plot_var[8]]), max(df_map[plot_var[9]]), max(df_map[plot_var[10]]), max(df_map[plot_var[11]]) ] # Make a selection of the date to plot slider = DateSlider(title='Date', start=first_dt, end=last_dt, step=1, value=last_dt, height=20, margin=(20, 50, 20, 50), sizing_mode="stretch_width") slider.on_change('value_throttled', update_map) # Make a span to show current date in plots dt_span = Span(location=slider.value_as_date, dimension='height', line_color='red', line_dash='solid', line_width=2) # Update timeseries plots based on selection source_map.selected.on_change('indices', update_plot) # Make a set of labels to show some totals on the map source_out = ColumnDataSource(get_stats()) columns_out = [ TableColumn(field='stat', title="Statistic"),
new_data = filtered_data(selectedDate) new_data['date'] = new_data['date'].to_string() new_json = new_data.to_json() geosource.geojson = new_json #Datepicker datepicker = DateSlider(start="2020-12-13", end=current_date, step=1, value=current_date, width=200, align="center", title="Currently Displaying") datepicker.on_change('value', update_plot) # Last Updated on Current Date Text update_text = "Last Updated on " + str(current_date) p.add_layout(Title(text=update_text, align="center", text_alpha=0.5), "below") #Authorship p.add_layout(Title(text="Made by Frank Hoang", align="center", text_alpha=0.5), "below") # Make a column layout of widgetbox(slider) and plot, and add it to the current document layout = Column(datepicker, p) curdoc().add_root(layout) show(layout) # In[ ]:
def update_date(attr, old, new): yr = date_slider.value new_data = json_data(yr) geosource.geojson = new_data p.title.text = 'covid 19 deaths, %s' %yr def update_color(attr, old, new): color_mapper.high = color_slider.value merged = read_all_data() geosource = GeoJSONDataSource(geojson = json_data(str(date(2020, 4,25)))) palette = brewer['YlGnBu'][8] palette = palette[::-1] color_cap_deaths = round(np.nanmax(merged["Deaths"]) + 500, -3) color_mapper = LinearColorMapper(palette = palette, low = 0, high = color_cap_deaths, nan_color = '#d9d9d9') hover = HoverTool(tooltips = [ ('Country/region','@country'),('deaths', '@Deaths')]) color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 900, height = 20, border_line_color=None,location = (0,0), orientation = 'horizontal') p = figure(title = 'Covid 19 deaths', plot_height = 900 , plot_width = 1600, tools = [BoxZoomTool(), ResetTool(), hover]) p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None p.patches('xs','ys', source = geosource,fill_color = {'field' :'Deaths', 'transform' : color_mapper}, line_color = 'black', line_width = 0.25, fill_alpha = 1) p.add_layout(color_bar, 'below') date_slider = DateSlider(title="Date Range: ", start=date(2020, 1, 31), end=date(2020, 4, 25), value=date(2020, 4, 25), step=1) date_slider.on_change('value', update_date) color_slider = Slider(title="Max value", start=0, end=color_cap_deaths, value=color_cap_deaths, step=100) color_slider.on_change('value', update_color) layout = column(p,widgetbox(date_slider), widgetbox(color_slider)) curdoc().add_root(layout) show(layout)
def build_time_evolution_tab(): # Importing geographical shapefile s3_root = 'https://covid19-bokeh-app.s3.eu-west-2.amazonaws.com' geo_data_gdf = gpd.read_file(f'{s3_root}/data/_geo_data/ne_50m_land.zip') geosource = GeoJSONDataSource(geojson=geo_data_gdf.to_json()) # Importing geo-evolutions cases/deaths data time_evol_df = pd.read_csv(f'{s3_root}/data/geo_time_evolution.csv') # Selecting earliest snapshot time_evol_df.date = pd.to_datetime(time_evol_df.date, format="%Y-%m-%d") snapshot_df = time_evol_df[time_evol_df.date == min(time_evol_df.date)] global_by_day_df = pd.read_csv(f'{s3_root}/data/global_by_day.csv') global_by_day_df.date = pd.to_datetime(global_by_day_df.date, format="%Y-%m-%d") global_totals_df = global_by_day_df.loc[global_by_day_df.date == min( time_evol_df.date)] global_cases = int(global_totals_df.iloc[0]['cases']) global_deaths = int(global_totals_df.iloc[0]['deaths']) # Applying bubble-size mapping bubble_size = snapshot_df['cases'].apply(lambda x: 0.5 * math.log(x, 1.1) if x > 0 else 0) snapshot_df = snapshot_df.assign(size=bubble_size.values) # Creating ColumnDataSource for visualisation cases_cds = ColumnDataSource(snapshot_df) # Adding figure and geographical patches from shapefile geo_plot = figure(plot_height=450, plot_width=720, x_range=(-180, 180), y_range=(-90, 90), name="time_evolution_geo_plot", sizing_mode="scale_width") geo_patches = geo_plot.patches('xs', 'ys', source=geosource, alpha=0.5) # Adding circle glyph to create bubble plot cases_circles = geo_plot.circle(x='long', y='lat', size='size', source=cases_cds, color='red', alpha=0.2) # Adding hover tool hover = HoverTool(tooltips=[('Country/Region', '@region'), ('Province/State', '@province'), ('Cases', '@cases')], renderers=[cases_circles]) geo_plot.add_tools(hover) # Adding hbar countries_df = snapshot_df.loc[:, ['date', 'region', 'cases']] countries_df.rename(columns={"cases": "value"}, inplace=True) countries_df = countries_df.groupby(['date', 'region']).sum().reset_index() countries_df.sort_values(by='value', ascending=False, inplace=True) countries_df = countries_df.reset_index(drop=True) countries_cds = ColumnDataSource(countries_df) hbar_plot = figure(plot_height=450, plot_width=475, y_range=(10.5, -0.5), x_range=(0, countries_df.value.max() * 1.2), name="time_evolution_hbar_plot", sizing_mode="scale_width") hbar = hbar_plot.hbar(left=0, right='value', y='index', source=countries_cds, height=0.5, line_color='white') labels = LabelSet(x='value', y='index', text='region', text_font_size='10pt', text_color='white', x_offset=5, y_offset=0, source=countries_cds, level='glyph', render_mode='canvas') hbar_plot.add_layout(labels) # Adding hover tool hover_hbar = HoverTool(tooltips=[('Country/Region', '@region'), ('Cases', '@value')], renderers=[hbar]) hbar_plot.add_tools(hover_hbar) # Adding callback for updating data def data_view_callback(attr, old, new): """Callback function to update data source: - Updates source data to selected data view (cases/deaths/new cases/new deaths) and selected snapshot date on date slider. - Updates HoverTool to reflect data view change - Updates Divs for total cases/deaths """ # Determine data view selection if cases_deaths_button.active == 0: data_view = "cases" elif cases_deaths_button.active == 1: data_view = "deaths" if total_new_button.active == 1: data_view = f"new_{data_view}" # Determine date selection slider_date = date_slider.value_as_datetime.date() # Filter data for selected date snapshot_df = time_evol_df[time_evol_df.date == pd.Timestamp( slider_date)] # Map bubble size on selected data view bubble_size = snapshot_df[data_view].apply( lambda x: 0.5 * math.log(x, 1.1) if x > 0 else 0) snapshot_df = snapshot_df.assign(size=bubble_size.values) cases_cds.data = snapshot_df hover.tooltips = [('Country/Region', '@region'), ('Province/State', '@province'), (data_view.replace('_', ' ').title(), f'@{data_view}')] # Update hbar data countries_df = snapshot_df.loc[:, ['date', 'region', data_view]] countries_df.rename(columns={data_view: "value"}, inplace=True) countries_df = countries_df.groupby(['date', 'region']).sum().reset_index() countries_df.sort_values(by="value", ascending=False, inplace=True) countries_df = countries_df.reset_index(drop=True) countries_cds.data = countries_df hbar_plot.x_range.end = countries_df.value.max() * 1.2 hover_hbar.tooltips = [('Country/Region', '@region'), (data_view.replace('_', ' ').title(), '@value')] # Filter for totals global_totals_df = global_by_day_df.loc[( global_by_day_df.date == pd.Timestamp(slider_date))] global_cases = int(global_totals_df.iloc[0]['cases']) global_deaths = int(global_totals_df.iloc[0]['deaths']) cases_div.text = (f'<h3 class="card-text">' f'{global_cases:,}</h3>') deaths_div.text = (f'<h3 class="card-text">' f'{global_deaths:,}</h3>') # Adding Date slider date_range = [ pd.Timestamp(date_val) for date_val in time_evol_df.date.unique() ] date_slider = DateSlider(title="Date", start=min(date_range), end=max(date_range), value=min(date_range), sizing_mode="scale_width") date_slider.on_change('value', data_view_callback) # Adding Cases/Deaths toggle cases_deaths_button = RadioButtonGroup(labels=["Cases", "Deaths"], active=0, sizing_mode="scale_width") cases_deaths_button.on_change('active', data_view_callback) # Adding Total/New toggle total_new_button = RadioButtonGroup(labels=["Total", "New"], active=0, sizing_mode="scale_width") total_new_button.on_change('active', data_view_callback) # Adding callback for zooming into a selected continent def continent_zoom_callback(attr, old, new): continent_map_ref = { # Worldwide 0: { 'x_range': [-200, 200], 'y_range': [-100, 100] }, # Europe 1: { 'x_range': [-30, 50], 'y_range': [30, 70] }, # North America 2: { 'x_range': [-175, -15], 'y_range': [0, 80] }, # South America 3: { 'x_range': [-140, 10], 'y_range': [-60, 15] }, # Africa 4: { 'x_range': [-55, 105], 'y_range': [-40, 40] }, # Asia 5: { 'x_range': [40, 140], 'y_range': [-5, 45] }, # Oceania 6: { 'x_range': [80, 200], 'y_range': [-55, 5] } } map_ref = continent_map_ref[continent_button.active] geo_plot.x_range.start = map_ref['x_range'][0] geo_plot.x_range.end = map_ref['x_range'][1] geo_plot.y_range.start = map_ref['y_range'][0] geo_plot.y_range.end = map_ref['y_range'][1] # Adding continent toggle continent_button = RadioGroup(labels=[ "Worldwide", "Europe", "North America", "South America", "Africa", "Asia", "Oceania" ], active=0) continent_button.on_change('active', continent_zoom_callback) # Adding animation with Play/Pause button callback_id = None def animate(): def animate_update(): date = date_slider.value_as_datetime.date() + timedelta(days=1) date = pd.Timestamp(date) if date >= max(date_range): date = min(date_range) date_slider.value = date global callback_id if play_button.label == '► Play': play_button.label = '❚❚ Pause' callback_id = curdoc().add_periodic_callback(animate_update, 300) else: play_button.label = '► Play' curdoc().remove_periodic_callback(callback_id) play_button = Button(label='► Play', width=60, button_type="success") play_button.on_click(animate) # Adding Cases/Deaths count cases_div = Div(text=f'<h3 class="card-text">' f'{global_cases:,}</h3>', sizing_mode="scale_width", name="cases_div") deaths_div = Div(text=f'<h3 class="card-text">' f'{global_deaths:,}</h3>', sizing_mode="scale_width", name="deaths_div") # Defining layout of tab widgets = widgetbox(date_slider, cases_deaths_button, total_new_button, play_button, continent_button, name="time_evolution_widgetbox", sizing_mode="scale_width") # Add the plot to the current document curdoc().add_root(geo_plot) curdoc().add_root(hbar_plot) curdoc().add_root(widgets) curdoc().add_root(cases_div) curdoc().add_root(deaths_div)
def create_us_map_tab(): "Factory for creating second tab of app: US Only Data" ## Data Sources source_df_confirmed, source_CDS = get_time_series_confirmed_US_data() source_CDS.data['number_per_capita'] = source_df_confirmed[ START_DATE_STRING] / source_df_confirmed['population'] ## Map color_mapper = log_cmap(field_name='number', palette=Spectral6, low=1, high=1e6) TOOLTIPS = [('County/Region', '@county'), ('State/Province', '@region'), ('Population', '@population'), ('Cases', '@number'), ('Cases Per Capita', '@number_per_capita')] map_figure = figure( title='Confirmed COVID-19 Cases in the United States', tooltips=TOOLTIPS, x_range=(-18367715, -6901808.43), y_range=(0, 13377019.78), x_axis_type='mercator', y_axis_type='mercator', active_scroll='wheel_zoom', ) tile_provider = get_provider(CARTODBPOSITRON) map_figure.add_tile(tile_provider) map_figure.circle(x='web_mercator_x', y='web_mercator_y', source=source_CDS, size='sizes', color=color_mapper) ## Colorbar color_bar = ColorBar(title='Num. Cases', title_standoff=20, color_mapper=color_mapper['transform'], label_standoff=20, width=8, location=(0, 0), ticker=LogTicker()) color_bar.formatter.use_scientific = False map_figure.add_layout(color_bar, 'right') ## Slider def slider_callback(attr, old, new): delta = datetime.timedelta(milliseconds=new) date = datetime.date(1970, 1, 1) + delta date_string = date.strftime('%Y-%m-%d') try: source_CDS.data['number'] = source_df_confirmed[date_string] source_CDS.data['sizes'] = source_df_confirmed[date_string].apply( scale) source_CDS.data['number_per_capita'] = source_df_confirmed[ date_string] / source_df_confirmed['population'] except KeyError: pass slider = DateSlider(title='Date', start=START_DATE, end=datetime.date.today(), step=1, value=START_DATE) slider.on_change('value', slider_callback) ## Data Table columns = [ TableColumn(field='county', title='County/Region'), TableColumn(field='region', title='State/Province'), TableColumn(field='population', title='Population'), TableColumn(field='number', title='Cases'), TableColumn(field='number_per_capita', title='Cases Per Capita'), ] data_table = DataTable( source=source_CDS, columns=columns, ) ## Cancel Selection Button def cancel_selection_callback(): source_CDS.selected.indices = [] cancel_selection_button = Button(label='Clear Selection', button_type='warning') cancel_selection_button.on_click(cancel_selection_callback) child = row([ column([slider, map_figure]), column([data_table, cancel_selection_button]) ]) return Panel(child=child, title='United States Map')
# update columns 'size', 'dnc' with the column named '%Y-%m-%d' in merged # update geosource with new merged def callback(attr, old, new): # Convert timestamp to datetime # https://stackoverflow.com/questions/9744775/how-to-convert-integer-timestamp-to-python-datetime date = datetime.fromtimestamp(new / 1e3) i = date.strftime('%Y-%m-%d') merged.size = merged[i] * 1e5 / 5 + 10 merged.dnc = merged[i] geosource.geojson = merged.to_json() # Circles change on mouse move timeslider.on_change('value', callback) # T2.6 Add a play button to change slider value and update the map plot dynamically # https://stackoverflow.com/questions/46420606/python-bokeh-add-a-play-button-to-a-slider # https://stackoverflow.com/questions/441147/how-to-subtract-a-day-from-a-date # Update the slider value with one day before current date # somehow the timeslider.value is also an int and thus needs to be converted def animate_update_slider(): # Extract date from slider's current value and convert it to datetime date = datetime.fromtimestamp(timeslider.value / 1e3) # Subtract one day from date and do not exceed the allowed date range day = date - timedelta(days=1) day = day.replace(hour=0, minute=0, second=0, microsecond=0) # Handle out of range
def create_world_map_tab(): "Factory for creating first tab of app." ## Data Sources source_df, source_CDS = get_time_series_confirmed_data() ## Map color_mapper = log_cmap(field_name='number', palette=Spectral6, low=1, high=1e6) TOOLTIPS = [('Region', '@full_name'), ('Num. Cases', '@number{0,0}')] map_figure = figure(title='Confirmed COVID-19 Cases by Region', tooltips=TOOLTIPS, x_range=(-16697923.62, 18924313), y_range=(-8399737.89, 8399737.89), x_axis_type='mercator', y_axis_type='mercator', active_scroll='wheel_zoom') tile_provider = get_provider(CARTODBPOSITRON) map_figure.add_tile(tile_provider) map_figure.circle(x='web_mercator_x', y='web_mercator_y', source=source_CDS, size='sizes', color=color_mapper) ## Colorbar color_bar = ColorBar(title='Num. Cases', title_standoff=20, color_mapper=color_mapper['transform'], label_standoff=20, width=8, location=(0, 0), ticker=LogTicker()) color_bar.formatter.use_scientific = False map_figure.add_layout(color_bar, 'right') ## Slider def slider_callback(attr, old, new): delta = datetime.timedelta(milliseconds=new) date = datetime.date(1970, 1, 1) + delta date_string = date.strftime('%Y-%m-%d') try: source_CDS.data['number'] = source_df[date_string] source_CDS.data['sizes'] = source_df[date_string].apply(scale) except KeyError: pass slider = DateSlider(title='Date', start=START_DATE, end=datetime.date.today(), step=1, value=START_DATE) slider.on_change('value', slider_callback) ## Data Table columns = [ TableColumn(field='full_name', title='Region'), TableColumn(field='number', title='Cases') ] data_table = DataTable( source=source_CDS, columns=columns, ) ## Cancel Selection Button def cancel_selection_callback(): source_CDS.selected.indices = [] cancel_selection_button = Button(label='Clear Selection', button_type='warning') cancel_selection_button.on_click(cancel_selection_callback) child = row([ column([slider, map_figure]), column([data_table, cancel_selection_button]), ]) return Panel(child=child, title='World Map')
update_legend_color_bar(select_feature.value, day) def update_src_after_day_change(attr, old, new): day = ts_to_day(new) geosource.geojson = create_sample(day).to_json() # def update_label_figures_after_feature_change(attr, old, new): # label_figures.text = new TOOLTIPS = [('Область', '@ADM1_UA'), (select_feature.value, '@{' + str(select_feature.value) + '}')] plot.add_tools(HoverTool(tooltips=TOOLTIPS)) tabs.on_change('active', update_tabs) select_feature.on_change('value', update_color_mapper_and_legend_color_bar_after_feature_change) select_feature.on_change('value', update_tooltips_after_feature_change) select_feature.js_link('value', plot.title, 'text') # select_feature.on_change('value', update_label_figures_after_feature_change) slider_time.on_change('value_throttled', update_src_after_day_change) slider_time.on_change('value_throttled', update_color_mapper_and_legend_color_bar_after_day_change) curdoc().add_root(column(tabs)) curdoc().title = 'Ukraine | Hospitals | Supply'
def _plot_choropleth(geoSource, prefectureBoundaries, geographicDistributionData): #Define a sequential multi-hue color palette and reverse order so that dark => high # of cases. base_colors = brewer['YlOrRd'][8] base_colors = base_colors[::-1] #Transform palette to create non-uniform intervals. palette, low, high = _transform_color_intervals(base_colors, 0, 800) #Instantiate LinearColorMapper that maps numbers in a range, into a sequence of colors. Input nan_color. color_mapper = LinearColorMapper(palette=palette, low=low, high=high, nan_color='#d9d9d9') #Add hover tool. hover = HoverTool(tooltips=[('prefecture', '@prefecture'), ('# of cases', '@cases')]) #Define custom ticks for colorbar. ticks = np.array([0, 20, 50, 100, 200, 400, 800]) #Create color bar. color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8, width=800, height=20, border_line_color=None, location=(0, 0), orientation='horizontal', ticker=FixedTicker(ticks=ticks)) #Create figure object. p = figure(title='COVID-19 cases in Greece, %s' % DATES[-1], plot_height=600, plot_width=950, toolbar_location=None, tools=[hover]) p.xgrid.grid_line_color = None p.ygrid.grid_line_color = None #Add patch renderer to figure. p.patches('xs', 'ys', source=geoSource, fill_color={ 'field': 'cases', 'transform': color_mapper }, line_color='black', line_width=0.25, fill_alpha=1) #Specify figure layout. p.add_layout(color_bar, 'below') def _update_plot(attr, old, new): """Callback function to update the plot using a slider. """ date = dt.utcfromtimestamp(slider.value / 1000).strftime('%Y_%m_%d') new_data = _merge_and_convert_to_json(prefectureBoundaries, geographicDistributionData, date) geoSource.geojson = new_data p.title.text = 'COVID-19 cases in Greece, %s' % date #Make a slider object to trigger _update_plot. slider = DateSlider(title='Date', start=dt.strptime(DATES[0], '%Y_%m_%d'), end=dt.strptime(DATES[-1], '%Y_%m_%d'), step=int( datetime.timedelta(days=1).total_seconds() * 1000), value=dt.strptime(DATES[-1], '%Y_%m_%d')) slider.on_change('value', _update_plot) #Make a column layout of plot and slider, and add it to the current document. layout = column(p, column(slider)) curdoc().add_root(layout) curdoc().title = 'COVID-19 cases in Greece' #Crete an HTML of the static choropleth for 2020-03-29. save(p, OUTPUT_FILE_PATH, resources=None, title='COVID-19 cases in Greece, 2020_03_29')
# update geosource with new merged def callback(attr, old, new): # Convert timestamp to datetime # https://stackoverflow.com/questions/9744775/how-to-convert-integer-timestamp-to-python-datetime date = ... i = date.strftime('%Y-%m-%d') merged.size = ... merged.dnc = ... geosource.geojson = ... # Circles change on mouse move timeslider.on_change(...) # T2.6 Add a play button to change slider value and update the map plot dynamically # https://stackoverflow.com/questions/46420606/python-bokeh-add-a-play-button-to-a-slider # https://stackoverflow.com/questions/441147/how-to-subtract-a-day-from-a-date # Update the slider value with one day before current date def animate_update_slider(): # Extract date from slider's current value date = ... # Subtract one day from date and do not exceed the allowed date range day = ... ...
def gen_bokeh( df, il_center=(3879662.3653308493, 3694724.561061665), marg_x=2.0e5, marg_y=2.0e5, init_date=datetime.datetime(year=2021, month=5, day=10), MAX_W=1000, MAX_H=800, ): # main figure with world map tile_provider = get_provider(CARTODBPOSITRON) p = figure(x_range=(il_center[0] - marg_x, il_center[0] + marg_x), y_range=(il_center[1] - marg_y, il_center[1] + marg_y), x_axis_type="mercator", y_axis_type="mercator") p.plot_height = MAX_H // 2 p.plot_width = MAX_W p.add_tile(tile_provider) # process df for plotting df['mydate'] = pd.to_datetime(df['date']) df = df.loc[df['mydate'] > init_date] merc = longlat2mercator.transform(df['lat'].values, df['long'].values) df['x'] = merc[0] df['y'] = merc[1] df['name'] = df['location'] df[['x', 'y', 'mydate_f', 'name']] = df[['x', 'y', 'mydate', 'name']].fillna('') df['fill_alpha'] = .5 df['line_alpha'] = .0 df['fill_color'] = df['eta'].apply(eta2color) src_cols = [ 'x', 'y', 'mydate_f', 'name', 'fill_alpha', 'line_alpha', 'fill_color' ] source0 = df[src_cols].to_dict('list') source = ColumnDataSource(data=source0) # add alarms on map p.circle(x="x", y="y", size=4, fill_color="fill_color", fill_alpha='fill_alpha', line_alpha='line_alpha', source=source) p.add_tools( HoverTool( tooltips=[ ('time', '@mydate_f{%H:%M}'), ('date', '@mydate_f{%d/%m/%Y}'), ('name', '@name'), ], formatters={'@mydate_f': 'datetime'}, )) # build other elements date_slider = DateSlider(start=df['mydate'].min(), end=date.today(), step=24, value=date.today(), title='Date', width=int(MAX_W * .9)) dur_slider = Slider(start=0, end=168, step=1, value=24, title='Hour duration', width=MAX_W // 2) delay_slider = Slider(start=0, end=1000, step=10, value=200, title='Animation delay (ms)', width=MAX_W // 2) label_xy = longlat2mercator.transform(31., 34) label_src = ColumnDataSource(data=dict(text=['Hello, use the sliders'])) label = LabelSet(text='text', x=label_xy[0], y=label_xy[1], source=label_src) p.add_layout(label) button = Button(label="Play", button_type="success", width=MAX_W // 10) # add plot for number of alarms per day ff = figure(x_range=(df['mydate_f'].min(), df['mydate_f'].max()), x_axis_type="datetime") df['day'] = df['mydate_f'].dt.day agg = df.groupby('day').agg('count') agg['date'] = agg.index.to_series().apply( lambda x: df.loc[df['mydate_f'].dt.day == x, 'mydate_f'].iloc[0]) ff.scatter(agg['date'], agg['Unnamed: 0'], size=10) ff.plot_height = MAX_H // 3 ff.plot_width = MAX_W ff.title = 'alarms per day' # callback functions callback_id = None def update_map_callback(attr, old, new): st, en = date_slider.value, date_slider.value + dur_slider.value * 60 * 60 * 1000 st = datetime.datetime.fromtimestamp(st / 1000.) en = datetime.datetime.fromtimestamp(en / 1000.) ddf = df.loc[(df['mydate_f'] >= st) & (df['mydate_f'] < en)] source.data = ddf[src_cols].to_dict('list') sts = st.strftime("%d/%m, %H:%M") ens = en.strftime("%d/%m, %H:%M") label_src.data = dict( text=[f'Range: {sts} to {ens}, total: {len(ddf)}']) def restart_animation(attr, old, new): global callback_id if button.label == 'Stop': curdoc().remove_periodic_callback(callback_id) callback_id = curdoc().add_periodic_callback( adv_slider, delay_slider.value) def adv_slider(): date_slider.value += 1 * 1000 * 60 * 60 if date_slider.value >= date_slider.end: date_slider.value = date_slider.start def button_callback(): global callback_id if button.label == 'Play': button.label = 'Stop' callback_id = curdoc().add_periodic_callback( adv_slider, delay_slider.value) else: button.label = 'Play' curdoc().remove_periodic_callback(callback_id) # callback assignment delay_slider.on_change('value', restart_animation) date_slider.on_change('value', update_map_callback) dur_slider.on_change('value', update_map_callback) button.on_click(button_callback) lay = column(p, row(date_slider, button), row(dur_slider, delay_slider), ff) return lay