Deploying bokeh dashboard via Jupyter notebook; plots not updating - python
I have been mainly working in VS code to create a bokeh dashboard and I now need to get it to run within a Jupyter notebook. I know that some transformations in the code are required to push the code to a Jupyter notebook and for it to update interactively with widgets.
I have referred to this documentation:-
https://docs.bokeh.org/en/latest/docs/user_guide/jupyter.html#userguide-jupyter-notebook
But it is either too simplistic for my code, or that I have not used the push_notebook commands properly (or both).
Here is the code that I am trying to run in the notebook:-
################################### Code chunk 1##########################
from ipywidgets import interact
import pandas as pd
import numpy as np
import math
from bokeh.models import HoverTool
from bokeh.io import curdoc, output_notebook, push_notebook
from bokeh.plotting import figure, ColumnDataSource
from bokeh.layouts import layout, row, column, gridplot
from bokeh.models.widgets import RangeSlider
#https://discourse.bokeh.org/t/interactive-histograms-not-updating-with-sliders/3779/25
#clustering packages
from operator import index
from bokeh.models.widgets.markups import Div
import numpy as np
from numpy.lib import source
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import column, row, gridplot, Column, Row
from bokeh.models import ColumnDataSource, Select, Slider, BoxSelectTool, LassoSelectTool, Tabs, Panel, LinearColorMapper, ColorBar, BasicTicker, PrintfTickFormatter, MultiSelect, DataTable, TableColumn
from bokeh.plotting import figure, curdoc, show
from bokeh.palettes import viridis, gray, cividis, Category20
from bokeh.transform import factor_cmap
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import classification_report, confusion_matrix, mean_squared_error, r2_score, recall_score, f1_score
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.decomposition import PCA
#tables
from time import time
output_notebook()
################################# Code chunk 2 ###########################################
#define the categorical variable
category_a = ['A','B','C']
category_b = ['X','Y','Z']
print("step 2")
df = pd.DataFrame({
'id': np.arange(0, 100),
'date': pd.date_range(start='1/1/2021', periods=100, freq='D'),
'month':np.random.randint(1, 12, 100),
'sensor_1': np.random.uniform(0, 1,100),
'sensor_2': np.random.uniform(10, 150, 100),
'sensor_3': np.random.randint(0, 90, 100),
'sensor_4': np.random.randint(0, 450, 100),
'sensor_5': np.random.randint(0, 352, 100),
'categorya': np.random.choice(category_a, 100, p=[0.2, 0.4, 0.4]),
'categoryb': np.random.choice(category_b, 100, p=[0.6, 0.2, 0.2]),
})
source = ColumnDataSource(data=df)
################################### Code chunk 3##############################
class hist_data:
def __init__(self, df, col, n_bins, bin_range):
self.sensor1_lwr = min(df['sensor_1'])#duration millisecond
self.sensor1_upr = max(df['sensor_1'])#duration millisecond
self.sensor2_lwr = min(df['sensor_2'])#count watch
self.sensor2_upr = max(df['sensor_2'])#count_watch
self.sensor3_lwr = min(df['sensor_3'])#count idle
self.sensor3_upr = max(df['sensor_3'])#count idle
self.sensor4_lwr = min(df['sensor_4'])#count inter and watch
self.sensor4_upr = max(df['sensor_4'])#count inter and watch
self.sensor5_lwr = min(df['sensor_5'])#count inter
self.sensor5_upr = max(df['sensor_5'])#count inter
self.col = col
self.n_bins = n_bins
self.bin_range = bin_range
self.original_df = df
self.source = ColumnDataSource(self.create_hist_data(df))
def filt_df(self):
filt = (pd.DataFrame(self.original_df[(self.original_df.sensor_1 >=self.sensor1_lwr) &
(self.original_df.sensor_1 <= self.sensor1_upr) &
(self.original_df.sensor_2 >= self.sensor2_lwr) &
(self.original_df.sensor_2 <= self.sensor2_upr) &
(self.original_df.sensor_3 >= self.sensor3_lwr) &
(self.original_df.sensor_3 <= self.sensor3_upr) &
(self.original_df.sensor_4 >= self.sensor4_lwr) &
(self.original_df.sensor_4 <= self.sensor4_upr) &
(self.original_df.sensor_5 >= self.sensor5_lwr) &
(self.original_df.sensor_5 <= self.sensor5_upr)]))
print(f'{self.sensor1_lwr} {self.sensor1_upr} {self.sensor2_lwr} {self.sensor2_upr} {self.sensor3_lwr} {self.sensor3_upr} {self.sensor4_lwr} {self.sensor4_upr} {self.sensor5_lwr} {self.sensor5_upr}')
filt.shape
return ColumnDataSource(self.create_hist_data(filt))
def create_hist_data(self,df):
arr_hist, edges = np.histogram(df[self.col],bins=self.n_bins, range=self.bin_range)
arr_df = pd.DataFrame({'count': arr_hist, 'left': edges[:-1], 'right': edges[1:]})
arr_df['f_count'] = ['%d' % count for count in arr_df['count']]
arr_df['f_interval'] = ['%d to %d ' % (left, right) for left, right in zip(arr_df['left'], arr_df['right'])]
return (arr_df)
df = df
########################histograms
hist_data_A = hist_data(df,'sensor_1',math.floor(math.sqrt(df['sensor_1'].nunique())),[min(df['sensor_1']),max(df['sensor_1'])])
hist_data_B = hist_data(df,'sensor_2',math.floor(math.sqrt(df['sensor_2'].nunique())),[min(df['sensor_2']),max(df['sensor_2'])])
hist_data_C = hist_data(df,'sensor_3',math.floor(math.sqrt(df['sensor_3'].nunique())),[min(df['sensor_3']),max(df['sensor_3'])])
hist_data_D = hist_data(df,'sensor_4',math.floor(math.sqrt(df['sensor_4'].nunique())),[min(df['sensor_4']),max(df['sensor_4'])])
hist_data_E = hist_data(df,'sensor_5',math.floor(math.sqrt(df['sensor_5'].nunique())),[min(df['sensor_5']),max(df['sensor_5'])])
############################slider
A_Slider= RangeSlider(start=min(df['sensor_1']), end=max(df['sensor_1']), value=(min(df['sensor_1']),max(df['sensor_1'])), step=1, title='sensor_1')
B_Slider = RangeSlider(start=min(df['sensor_2']), end=max(df['sensor_2']), value=(min(df['sensor_2']),max(df['sensor_2'])), step=1, title='sensor_2')
C_Slider = RangeSlider(start=min(df['sensor_3']), end=max(df['sensor_3']), value=(min(df['sensor_3']),max(df['sensor_3'])), step=1, title='sensor_3')
D_Slider = RangeSlider(start=min(df['sensor_4']), end=max(df['sensor_4']), value=(min(df['sensor_4']),max(df['sensor_4'])), step=1, title='sensor_4')
E_Slider = RangeSlider(start=min(df['sensor_5']), end=max(df['sensor_5']), value=(min(df['sensor_5']),max(df['sensor_5'])), step=1, title='sensor_5')
def callback_A(attr,new,old):
hist_data_A.sensor1_lwr = new[0]
hist_data_A.sensor1_upr = new[1]
hist_data_A.source = hist_data_A.filt_df()
Graphs1.children[0] = plot_data_A()
push_notebook(handle=grid)
def callback_B(attr,new,old):
hist_data_B.sensor2_lwr = new[0]
hist_data_B.sensor2_upr = new[1]
hist_data_B.source = hist_data_B.filt_df()
Graphs1.children[1] = plot_data_B()
push_notebook(handle=grid)
def callback_C(attr,new,old):
hist_data_C.sensor3_lwr = new[0]
hist_data_C.sensor3_upr = new[1]
hist_data_C.source = hist_data_C.filt_df()
Graphs1.children[2] = plot_data_C()
push_notebook(handle=grid)
def callback_D(attr,new,old):
hist_data_D.sensor4_lwr = new[0]
hist_data_D.sensor4_upr = new[1]
hist_data_D.source = hist_data_D.filt_df()
Graphs2.children[0] = plot_data_D()
push_notebook(handle=grid)
def callback_E(attr,new,old):
hist_data_E.sensor5_lwr = new[0]
hist_data_E.sensor5_upr = new[1]
hist_data_E.source = hist_data_E.filt_df()
Graphs2.children[1] = plot_data_E()
push_notebook(handle=grid)
A_Slider.on_change("value",callback_A)
B_Slider.on_change("value",callback_B)
C_Slider.on_change("value",callback_C)
D_Slider.on_change("value",callback_D)
E_Slider.on_change("value",callback_E)
(df,'sensor_1',df['sensor_1'].nunique(),[min(df['sensor_1']),max(df['sensor_1'])])
(df,'sensor_2',df['sensor_2'].nunique(),[min(df['sensor_2']),max(df['sensor_2'])])
(df,'sensor_3',df['sensor_3'].nunique(),[min(df['sensor_3']),max(df['sensor_3'])])
(df,'sensor_4',df['sensor_4'].nunique(),[min(df['sensor_4']),max(df['sensor_4'])])
(df,'sensor_5',df['sensor_5'].nunique(),[min(df['sensor_5']),max(df['sensor_5'])])
# Histogram
def interactive_histogram( hist_data, title,x_axis_label,x_tooltip):
source = hist_data
# Set up the figure same as before
toollist = ['lasso_select', 'tap', 'reset', 'save','crosshair','wheel_zoom','pan','hover','box_select']
p = figure(plot_width = 400,
plot_height = 400,
title = title,
x_axis_label = x_axis_label,
y_axis_label = 'Count',tools=toollist)
# Add a quad glyph with source this time
p.quad(bottom=0,
top='count',
left='left',
right='right',
source=source,
fill_color='red',
hover_fill_alpha=0.7,
hover_fill_color='blue',
line_color='black')
# Add style to the plot
p.title.align = 'center'
p.title.text_font_size = '18pt'
p.xaxis.axis_label_text_font_size = '12pt'
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.axis_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
# Add a hover tool referring to the formatted columns
hover = HoverTool(tooltips = [(x_tooltip, '#f_interval'),
('Count', '#f_count')])
# Add the hover tool to the graph
p.add_tools(hover)
return p
push_notebook(handle=grid)
#binsize = 10
binzise = 100
def plot_data_A():
A_hist = interactive_histogram(hist_data_A.source, 'sensor_1','sensor_1','sensor_1')
return A_hist
def plot_data_B():
B_hist = interactive_histogram(hist_data_B.source, 'sensor_2','sensor_2','sensor_2')
return B_hist
#
def plot_data_C():
C_hist = interactive_histogram(hist_data_C.source, 'sensor_3','sensor_3','sensor_3')
return C_hist
#
def plot_data_D():
D_hist = interactive_histogram(hist_data_D.source, 'sensor_4','sensor_4','sensor_4')
return D_hist
#
def plot_data_E():
E_hist = interactive_histogram(hist_data_E.source, 'sensor_5','sensor_5','sensor_5')
return E_hist
#
Graphs1 = row([plot_data_A(), plot_data_B(), plot_data_C()])
Graphs2 = row([plot_data_D(), plot_data_E()])
Controls1= column([A_Slider,B_Slider,C_Slider,D_Slider,E_Slider])
#grid = gridplot([[Graphs1],
# [Controls1]])
grid = gridplot([[Controls1,Graphs1],[None,Graphs2]])
show(grid)
Now, it brings up the plots:-
But the widgets do not update the plots. Can someone kindly show me what I am missing?
Related
How to show sums at nodes from the dataframe using Hovertool?
I have created a Sankeydiagram using a Holomap and I want to show the sums of the respective nodes by hovering over them but I don't know how I need to format my code or setup the dataframe to achieve this. Github import holoviews as hv from holoviews import opts, dim import pandas as pd from bokeh.palettes import Viridis import bokeh.models from bokeh.themes import built_in_themes hv.extension('bokeh') df = pd.read_excel("Refugees_V9.xlsx") df = df.dropna() df['year'] = df['year'].astype(int) df['refugees'] = df['refugees'].astype(int) hover = bokeh.models.HoverTool(tooltips=[('Refugees', '#refugees'),]) hv_ds = hv.Dataset( data=df, kdims=['source', 'target', 'year'], vdims=['refugees'], ) hv.renderer('bokeh').theme = built_in_themes['dark_minimal'] def hook(plot, element): #plot.handles['text_1_glyph'].text_font = 'verdana' #plot.handles['text_1_glyph'].text_font_size = '12pt' plot.handles['text_1_glyph'].text_color = 'snow' #plot.handles['text_2_glyph'].text_font = 'verdana' #plot.handles['text_2_glyph'].text_font_size = '12pt' plot.handles['text_2_glyph'].text_color = 'white' graph = hv_ds.to(hv.Sankey) graph.opts( label_position='outer', bgcolor = "#2f2f2f", edge_color='target', node_color='target', show_values = False, cmap= Viridis[10], width=800, height=800, title = "Refugee migration into the Schengen-EU 2011-2022", node_sort=False, node_width = 20, #tools= ['hover'], default_tools = [hover], show_frame=False, edge_alpha = 0.8, edge_hover_fill_alpha = 1, node_alpha = 0.8, node_hover_fill_alpha = 0.95, label_text_font_size = "10pt", hooks=[hook], toolbar=None, ) hv.output(graph, widget_location="bottom") Data Created Sankey I want to show the sum of refugees over the nodes and the connecting line displaying the information how many are migrating from where to where.
How to update my Bokeh Legend to reflect Categorical Variable in Pandas Dataframe
I'm trying to make a dropdown menu with Bokeh that highlights the points in clusters I found. I have the dropdown menu working, but now I want to be able to visualize another categorical variable by color: Noun Class with levels of Masc, Fem, and Neuter. The problem is that the legend won't update when I switch which cluster I'm visualizing. Furthermore, if the first cluster I visualize doesn't have all 3 noun classes in it, the code starts treating all the other clusters I try to look at as (incorrectly) having that first cluster's noun class. For example, if Cluster 0 is the default and only has Masc points, all other clusters I look at using the dropdown menu are treated as only having Masc points even if they have Fem or Neuter in the actual DF. My main question is this: how can I update the legend such that it's only attending to the respective noun classes of 'Curr' Here's some reproducible code: import pandas as pd from bokeh.io import output_file, show, output_notebook, save, push_notebook from bokeh.models import ColumnDataSource, Select, DateRangeSlider, CustomJS from bokeh.plotting import figure, Figure, show from bokeh.models import CustomJS from bokeh.layouts import row,column,layout import random import numpy as np from bokeh.transform import factor_cmap from bokeh.palettes import Colorblind import bokeh.io from bokeh.resources import INLINE #Generate reproducible DF noun_class_names = ["Masc","Fem","Neuter"] x = [random.randint(0,50) for i in range(100)] y = [random.randint(0,50) for i in range(100)] rand_clusters = [str(random.randint(0,10)) for i in range(100)] noun_classes = [random.choice(noun_class_names) for i in range(100)] df = pd.DataFrame({'x_coord':x, 'y_coord':y,'noun class':noun_classes,'cluster labels':rand_clusters}) df.loc[df['cluster labels'] == '0', 'noun class'] = 'Masc' #ensure that cluster 0 has all same noun class to illustrate error clusters = [str(i) for i in range(len(df['cluster labels'].unique()))] cols1 = df#[['cluster labels','x_coord', 'y_coord']] cols2 = cols1[cols1['cluster labels'] == '0'] Overall = ColumnDataSource(data=cols1) Curr = ColumnDataSource(data=cols2) #plot and the menu is linked with each other by this callback function callback = CustomJS(args=dict(source=Overall, sc=Curr), code=""" var f = cb_obj.value sc.data['x_coord']=[] sc.data['y_coord']=[] for(var i = 0; i <= source.get_length(); i++){ if (source.data['cluster labels'][i] == f){ sc.data['x_coord'].push(source.data['x_coord'][i]) sc.data['y_coord'].push(source.data['y_coord'][i]) sc.data['noun class'].push(source.data['noun class'][i]) sc.data['cluster labels'].push(source.data['cluster labels'][i]) } } sc.change.emit(); """) menu = Select(options=clusters, value='0', title = 'Cluster #') # create drop down menu bokeh_p=figure(x_axis_label ='X Coord', y_axis_label = 'Y Coord', y_axis_type="linear",x_axis_type="linear") #creating figure object mapper = factor_cmap(field_name = "noun class", palette = Colorblind[6], factors = df['noun class'].unique()) #color mapper for noun classes bokeh_p.circle(x='x_coord', y='y_coord', color='gray', alpha = .5, source=Overall) #plot all other points in gray bokeh_p.circle(x='x_coord', y='y_coord', color=mapper, line_width = 1, source=Curr, legend_group = 'noun class') # plotting the desired cluster using glyph circle and colormapper bokeh_p.legend.title = "Noun Classes" menu.js_on_change('value', callback) # calling the function on change of selection bokeh.io.output_notebook(INLINE) show(layout(menu,bokeh_p), notebook_handle=True) Thanks in advance and I hope you have a nice day :)
Imma keep it real with y'all... The code works how I want now and I'm not entirely sure what I did. What I think I did was reset the noun classes in the Curr data source and then update the legend label field after selecting a new cluster to visualize and updating the xy coords. If anyone can confirm or correct me for posterity's sake I would appreciate it :) Best! import pandas as pd import random import numpy as np from bokeh.plotting import figure, Figure, show from bokeh.io import output_notebook, push_notebook, show, output_file, save from bokeh.transform import factor_cmap from bokeh.palettes import Colorblind from bokeh.layouts import layout, gridplot, column, row from bokeh.models import ColumnDataSource, Slider, CustomJS, Select, DateRangeSlider, Legend, LegendItem import bokeh.io from bokeh.resources import INLINE #Generate reproducible DF noun_class_names = ["Masc","Fem","Neuter"] x = [random.randint(0,50) for i in range(100)] y = [random.randint(0,50) for i in range(100)] rand_clusters = [str(random.randint(0,10)) for i in range(100)] noun_classes = [random.choice(noun_class_names) for i in range(100)] df = pd.DataFrame({'x_coord':x, 'y_coord':y,'noun class':noun_classes,'cluster labels':rand_clusters}) df.loc[df['cluster labels'] == '0', 'noun class'] = 'Masc' #ensure that cluster 0 has all same noun class to illustrate error clusters = [str(i) for i in range(len(df['cluster labels'].unique()))] cols1 = df#[['cluster labels','x_coord', 'y_coord']] cols2 = cols1[cols1['cluster labels'] == '0'] Overall = ColumnDataSource(data=cols1) Curr = ColumnDataSource(data=cols2) #plot and the menu is linked with each other by this callback function callback = CustomJS(args=dict(source=Overall, sc=Curr), code=""" var f = cb_obj.value sc.data['x_coord']=[] sc.data['y_coord']=[] sc.data['noun class'] =[] for(var i = 0; i <= source.get_length(); i++){ if (source.data['cluster labels'][i] == f){ sc.data['x_coord'].push(source.data['x_coord'][i]) sc.data['y_coord'].push(source.data['y_coord'][i]) sc.data['noun class'].push(source.data['noun class'][i]) sc.data['cluster labels'].push(source.data['cluster labels'][i]) } } sc.change.emit(); bokeh_p.legend.label.field = sc.data['noun class']; """) menu = Select(options=clusters, value='0', title = 'Cluster #') # create drop down menu bokeh_p=figure(x_axis_label ='X Coord', y_axis_label = 'Y Coord', y_axis_type="linear",x_axis_type="linear") #creating figure object mapper = factor_cmap(field_name = "noun class", palette = Colorblind[6], factors = df['noun class'].unique()) #color mapper- sorry this was a thing that carried over from og code (fixed now) bokeh_p.circle(x='x_coord', y='y_coord', color='gray', alpha = .05, source=Overall) bokeh_p.circle(x = 'x_coord', y = 'y_coord', fill_color = mapper, line_color = mapper, source = Curr, legend_field = 'noun class') bokeh_p.legend.title = "Noun Classes" menu.js_on_change('value', callback) # calling the function on change of selection bokeh.io.output_notebook(INLINE) show(layout(menu,bokeh_p), notebook_handle=True)
IndexingError: Too many indexers , appears while creating plot
I've found a problem while learning about creating the financial plots. This code is a part of tutorial, but in my case it doesn't work. I tried each option about indexing like: .iloc , .values , (axis = 0) and everyting what had been written on the net. Could you help me ? Thank you in advance import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates import seaborn as sns import numpy as np sns.set(style = 'darkgrid', context = 'talk', palette = 'Dark2') data = pd.read_pickle('data.pickle') data = data['Close'] data = data['MSFT'] my_year_month_fmt = mdates.DateFormatter('%m/%y') short_SMA = data.rolling(window = 20, min_periods = 0).mean() long_SMA = data.rolling(window = 100, min_periods = 0).mean() short_EMA = data.ewm(span = 20, adjust = False, min_periods = 0).mean() long_EMA = data.ewm(span = 100, adjust = False, min_periods = 0).mean() start = pd.datetime(2015,1,1) end = pd.datetime(2020,4,21) data_EMA_difference = data - short_EMA trading_positions = data_EMA_difference.apply(np.sign) * 1/3 trading_positions_final = trading_positions.shift(1) fig, (ax1,ax2) = plt.subplots(2,1,figsize = (16,9)) ax1.plot(data.loc[start:end, :].index, data.loc[start:end,"MSFT"], label = "Price") ax1.plot(short_EMA.loc[start:end, :].index, short_EMA.loc[start:end,"MSFT"], label = "20 EMA") ax1.set_ylabel("Price in $") ax1.legend(loc = "best") ax1.xaxis.set_major_formatter(my_year_month_fmt) ax2.plot(trading_positions_final.loc[start:end, :].index, trading_positions_final.loc[start:end, "MSFT"], label = "Positions") ax2.set_ylabel('Positions') ax2.xaxis.set_major_formatter(my_year_month_fmt) plt.show()
How to use custom axis ticks on Bokeh?
I tried to specify x and y axis ticks on Bokeh plot as [0,25,50,75,100] and try major_label_overrides x as distance {0:'alone(0)',25: 'not close(25)', 50: 'alright close(50)', 75: 'middle close(75)', 100:'very close(100)'}, y axis custom as frequency {0:'never',25: 'once a year', 50: 'once a month', 75: 'once a week', 100:'everyday(100)'}. However, it shows an error. Thank you. ValueError: expected an instance of type Ticker, got [25, 50, 75, 100] of type list I have tried p.xaxis.ticker = FixedTicker(ticks=[0, 25, 50,75,100]) it fixes the tick problem but I can't customise it to frequency. Below is my code and github repository. https://github.com/Lastget/Covid19_Job_risks.git import matplotlib.pyplot as plt import numpy as np import seaborn as sns from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.models import ColumnDataSource, Select from bokeh.models import HoverTool, Label, LabelSet from bokeh.io import curdoc from bokeh.layouts import row from bokeh.models.renderers import GlyphRenderer from math import pi from bokeh.models import FixedTicker # Improt files expose = pd.read_csv(r'Exposed_to_Disease_or_Infections.csv',encoding='gbk') expose.head() #context, code, occupation expose.shape #(968,3) physical = pd.read_csv('Physical_Proximity.csv') physical.head() physical.shape #(967,3) TW_job = pd.read_excel('Small_Chinese.xlsx',encoding='utf-8') TW_job.shape #(968,3) TW_job = TW_job.iloc[:,:2] TW_job.head() #Merge temp_df = pd.merge(expose,physical,on=['Code','Occupation']) temp_df.head() temp_df.columns=['Expose_frequency','Code','Occupation','Physical_proximity'] temp_df.head() full_table = temp_df.merge(TW_job,how='left',on='Code') full_table.shape #967,5 # Delete Expose frequency for Rock splitter, timing deivce full_table = full_table.iloc[:965,:] # change expose to int64 full_table['Expose_frequency']=full_table['Expose_frequency'].astype('int64') full_table.info() # Start plotting source = ColumnDataSource(full_table) p = figure(title="各職業對新型冠狀病毒之風險圖", x_axis_label='工作時與人接近程度', y_axis_label='工作時暴露於疾病頻率', plot_width=900, plot_height=600) p.circle('Physical_proximity','Expose_frequency', name = 'allcircle', size=10,fill_alpha=0.2, source=source, fill_color='gray', hover_fill_color='firebrick', hover_line_color="firebrick", line_color=None) hover = HoverTool(tooltips=[('職業','#TW_Occupation'),('Occupation','#Occupation'),('暴露於疾病指數','#Expose_frequency'),('與人接近距離指數','#Physical_proximity')]) p.add_tools(hover) p.xaxis.ticker = [0, 25, 50,75,100] p.xaxis.major_label_overrides = {0:'獨自工作(0)',25: '不近(25)', 50: '稍微近(50)', 75: '中等距離(75)', 100:'非常近(100)'} p.yaxis.ticker = [0, 25, 50,75,100] p.yaxis.major_label_overrides = {0:'從不(0)',25: '一年一次(25)', 50: '一個月一次(50)', 75: '一週一次(75)', 100:'每天(100)'} p.yaxis.major_label_orientation = pi/4 # remove tool bar p.toolbar.logo = None p.toolbar_location = None def remove_glyphs(figure, glyph_name_list): renderers = figure.select(dict(type=GlyphRenderer)) for r in renderers: if r.name in glyph_name_list: col = r.glyph.y r.data_source.data[col] = [np.nan] * len(r.data_source.data[col]) # Define a callback function def update_plot(attr, old, new): remove_glyphs(p,['point_select']) old_choice=full_table[full_table['TW_Occupation']==old] choice=full_table[full_table['TW_Occupation']==new] a=choice['Physical_proximity'] b=choice['Expose_frequency'] p.circle(a,b,size=10,fill_alpha=1,fill_color=None,line_color="firebrick", name='point_select') # Add Select select = Select(title='請選擇工作', options=sorted(full_table['TW_Occupation'].tolist()), value='') # Attach the update_plot callback to the 'value' property of select select.on_change('value', update_plot) #layout layout = row(p, select) # Add the plot to the current document curdoc().add_root(layout)
add hover tooltip to line chart in Bokeh made via a loop
I am made a bokeh graph using a 'for loop'. But this method prevents me from adding tooltips since using the # method for the hover tuple prevents me from adding a column name if it is a loop. Is there any way to add the value and name of each country to my tooltip in a 'for loop'? the # hover line below does not work. import pandas as pd url = 'https://www.bp.com/content/dam/bp/business-sites/en/global/corporate/xlsx/energy-economics/statistical-review/bp-stats-review-2018-all-data.xlsx' df = pd.read_excel(url, sheet_name = 'Gas Consumption - Bcf', skiprows = 2, skipfooter = 15) df = df.dropna(how='all').transpose() df = df.rename(columns=df.iloc[0]).drop(df.index[0]) df = df.reset_index() df.rename(columns = {'index': 'Year'}, inplace=True) df = df.drop(df.index[[53, 54, 55]]) df['Year'] = pd.to_datetime(df['Year'], format = '%Y') top_ten = df.tail(1).T.reset_index().iloc[1:,:] top_ten.columns = ['country', 'value'] top_ten = top_ten.sort_values(by = 'value', ascending= False) top_ten_list = top_ten['country'].tolist() top_ten_list = [x for x in top_ten_list if not 'Total' in x][0:10] from bokeh.plotting import figure, output_notebook, show, reset_output from bokeh.models import ColumnDataSource from bokeh.palettes import Category10 from bokeh.models import HoverTool import itertools from bokeh.models import Legend mypalette = Category10[10] output_notebook() q = figure(plot_width=700, plot_height=500, x_axis_type='datetime') for c, color in zip(top_ten_list, mypalette): q.line(df['Year'],df[c], legend=c, color = color, line_width = 3) #hover = HoverTool(tooltips = [('Date', '#Year{%Y}'), ('Country', '#c billion cubic feet per day')], formatters = {'Year' : 'datetime'}) q.add_tools(hover) q.legend.location = "top_left" q.xaxis.axis_label = "Date" q.yaxis.axis_label = "billion cubic feet per day" q.legend.click_policy="hide" show(q)
I replaced the for loop with a ColumnDataSource and multiline which makes it easy to add a hovertool. I also had to add some CustomJS because calling #x/#y from multiline shows all the x/y values. The CustomJS makes sure that it only shows the right x/y position. import pandas as pd from bokeh.plotting import figure, show, reset_output, output_notebook from bokeh.models import ColumnDataSource from bokeh.palettes import Category10 from bokeh.models import HoverTool, ColumnDataSource from bokeh.models.glyphs import MultiLine import itertools from bokeh.models import Legend from bokeh.models.tools import CustomJSHover url = 'https://www.bp.com/content/dam/bp/business-sites/en/global/corporate/xlsx/energy-economics/statistical-review/bp-stats-review-2018-all-data.xlsx' df = pd.read_excel(url, sheet_name = 'Gas Consumption - Bcf', skiprows = 2, skipfooter = 15) df = df.dropna(how='all').transpose() df = df.rename(columns=df.iloc[0]).drop(df.index[0]) df = df.reset_index() df.rename(columns = {'index': 'Year'}, inplace=True) df = df.drop(df.index[[53, 54, 55]]) top_ten = df.tail(1).T.reset_index().iloc[1:,:] top_ten.columns = ['country', 'value'] top_ten = top_ten[~top_ten.country.str.contains("Total")] top_ten = top_ten.sort_values(by = 'value', ascending= False) top_ten_list = top_ten['country'].tolist()[:10] top_ten = df[top_ten_list] y = [df[country].tolist() for country in top_ten.columns.tolist()] x, xLst = [], df['Year'].tolist() for i in range(10): x.append(xLst) x_custom = CustomJSHover(code=""" return '' + special_vars.data_x """) y_custom = CustomJSHover(code=""" return '' + special_vars.data_y """) data = {'x': x, 'y': y, 'color': Category10[10], 'name': top_ten_list} source = ColumnDataSource(data) output_notebook() q = figure(plot_width=700, plot_height=500) q.multi_line(xs='x', ys='y', line_color='color', legend='name', line_width = 3, source=source) q.add_tools(HoverTool( tooltips=[ ('Year', '#x{custom}'), ('Value', '#y{custom}'), ('Country', '#name')], formatters=dict(x=x_custom, y=y_custom) )) q.legend.location = "top_left" q.xaxis.axis_label = "Date" q.yaxis.axis_label = "billion cubic feet per day" q.legend.click_policy="hide" show(q)