add hover tooltip to line chart in Bokeh made via a loop - python

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)

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.

Deploying bokeh dashboard via Jupyter notebook; plots not updating

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?

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)

How to toggle between figures using a dropdown menu in Bokeh?

I have a figure where I already added buttons to display different plots. How can I add another couple figures, each having different sets of plots and switch between the figures using a dropdown menu? I'm trying to condense the code as much as possible, as to not be rewriting the same functions for each figure. What is the best way to go about that? In the sample code below I didn't include a slider and three buttons for the second figure, but I would like all figures to have them
import numpy as np
import pandas as pd
import warnings
from bokeh.layouts import widgetbox
from bokeh.plotting import figure, show, output_file, output_notebook
from bokeh.palettes import Spectral11, colorblind, Inferno, BuGn, brewer
from bokeh.models import HoverTool, value, LabelSet, Legend, ColumnDataSource, LinearColorMapper, BasicTicker, PrintfTickFormatter, ColorBar
from bokeh.models.widgets import DateRangeSlider, CheckboxButtonGroup
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.layouts import column, row
from json import loads
import ast
import datetime as dt
warnings.filterwarnings('ignore')
TOOLS = 'save,pan,box_zoom,reset,wheel_zoom'
p = figure(title="data", plot_height=400, tools=TOOLS, plot_width=1300)
start_date = dt.datetime.strptime('2019 04 15', '%Y %m %d')
end_date = dt.datetime.strptime('2019 04 18', '%Y %m %d')
t = np.arange(0.0, 2.0, 0.01)
dates = np.arange(start_date, end_date, np.timedelta64(1, 'h'),
dtype='datetime64')
x = np.sin(3*np.pi*t)[:72]
y = np.cos(3*np.pi*t)[:72]
z = np.cos(6*np.pi*t)[:72]
for c in [x, y, z]:
c[40:50] = np.nan
source = ColumnDataSource(data={'Date': dates, 'x': x, 'y': y, 'z': z})
p.xaxis.axis_label = 'Date'
p.yaxis.axis_label = 'Position (m)'
def add_plot(y, color):
new_plot = p.line(x='Date', y=y, line_width=1, color=color, source=source)
return new_plot
x = add_plot('x', 'red')
y = add_plot('y', 'green')
z = add_plot('z', 'blue')
checkbox = CheckboxButtonGroup(labels=['x', 'y', 'z'], active=[0, 1, 2])
checkbox.callback = CustomJS(args=dict(x=x, y=y, z=z), code="""
//console.log(cb_obj.active);
x.visible = false;
y.visible = false;
z.visible = false;
for (i in cb_obj.active) {
//console.log(cb_obj.active[i]);
if (cb_obj.active[i] == 0) {
x.visible = true;
} else if (cb_obj.active[i] == 1) {
y.visible = true;
} else if (cb_obj.active[i] == 2) {
z.visible = true;
}
}
""")
callback = CustomJS(args=dict(p=p), code="""
var a = cb_obj.value;
p.x_range.start = a[0];
p.x_range.end = a[1];
""")
range_slider = DateRangeSlider(start=start_date, end=end_date,
value=(start_date, end_date), step=1)
range_slider.js_on_change('value', callback)
def get_hovertools():
hovers = {'x': x, 'y': y, 'z': z}
for k, v in hovers.items():
hovers[k] = HoverTool(mode='vline', renderers=[v])
hovers[k].tooltips = [('Date', '#Date{%F %H:%M:%S.%u}'),
(k, '#{'+k+'}{%0.2f}m')]
hovers[k].formatters = {'Date': 'datetime', k: 'printf'}
p.add_tools(hovers[k])
get_hovertools()
# --------------------- second figure here --------------------------
p2 = figure(title="data", plot_height=400, tools=TOOLS, plot_width=1300)
start_date = dt.datetime.strptime('2019 04 15', '%Y %m %d')
end_date = dt.datetime.strptime('2019 04 18', '%Y %m %d')
t = np.arange(0.0, 2.0, 0.01)
dates = np.arange(start_date, end_date, np.timedelta64(1, 'h'),
dtype='datetime64')
x2 = [1]*72
y2 = [2]*72
z2 = [3]*72
source = ColumnDataSource(data={'Date': dates, 'x': x2, 'y': y2, 'z': z2})
def add_plot(y, color):
new_plot = p2.line(x='Date', y=y, line_width=1, color=color, source=source)
return new_plot
x2 = add_plot('x', 'red')
y2 = add_plot('y', 'green')
z2 = add_plot('z', 'blue')
layout = column(p, widgetbox(checkbox), widgetbox(range_slider),
p2)
show(layout)
This example shows how to add AND remove figure from a Bokeh document (Bokeh v1.1.0). It doesn't include widgets for clarity reason but you could add your widgets there as well using the same approach.
However, maybe you could consider tabs as an option. When using tabs you don't need to remove/add the root elements, those are continuously present on the separate tabs which user can switch. You can find tabs examples here
from bokeh.models import Select, Row, ColumnDataSource
from bokeh.plotting import figure, curdoc
import numpy as np
x = np.linspace(0, 4 * np.pi, 100)
source = ColumnDataSource(dict(x = x, y = np.cos(x)))
glyphs = ["line", "scatter"]
select = Select(title = "Select plot:", value = "", options = [""] + glyphs)
curdoc().add_root(Row(select, name = 'root'))
def callback(attr, old, new):
layouts = curdoc().get_model_by_name('root').children
for glyph in glyphs:
plot = curdoc().get_model_by_name(glyph)
if plot:
layouts.remove(plot)
if new:
p = figure(name = new)
exec(new + ' = p.' + new + '("x", "y", source = source)')
layouts.append(p)
select.on_change('value', callback)
Result:

Python bokeh slider not refreshing plot

I am creating a bokeh plot with a slider to refresh plot accordingly. There are 2 issues with the code posted.
1. The plot is not refreshed as per the slider. Please help in providing a fix for this issue.
2. Plot is not displayed with curdoc() when bokeh serve --show fn.ipynb is used
I'm trying to visualise this CSV file.
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool, Slider
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
#Creating Column Data Source
source = ColumnDataSource({
'x' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('District_Name')
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
#xmin, xmax = min(data.Crop_Year), max(data.Crop_Year)
#ymin, ymax = min(data.Production), max(data.Production)
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('District_Name')
}
source.data = new_data
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
layout = row(widgetbox(slider), p)
curdoc().add_root(layout)
show(layout)
Also tried a different option using CustomJS as below, but still no luck.
callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
var yr = slider.value;
var x = data['x']
var y = data['y']
'x' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Area;
'y' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Production;
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district');
}
source.change.emit();
""")
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
yr_slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year', callback=callback)
callback.args["slider"] = yr_slider
Had a lot of issues trying to execute your code and I have changed some things, so feel free to correct me if did something wrong.
The error was caused by the creation of the ColumnDataSource, I had to change the level value to Crop_Year instead of Year. The loc 'ABC' also caused an error so I removed that too (And I had to add source = ColumnDataSource({, you probably forgot to copy that)
I also added a dropdown menu so it's possible to only show the data from one district.
Also, I'm not quite sure if it's possible to start a bokeh server by supplying a .ipynb file to --serve. But don't pin me down on that, I never use notebooks. I've tested this with a .py file.
#!/usr/bin/python3
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
from bokeh.models.widgets import Select, Slider
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==2001]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')=='Tamil Nadu']
#Creating Column Data Source
source = ColumnDataSource({
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
glyphs = p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
#Update glyph locations
yr = slider.value
state = select.value
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')==state]
new_data = {
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
}
source.data = new_data
#Update colors
district_list = crop_dt.loc[([state]), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
glyphs.glyph.fill_color = dict(field='district', transform=color_mapper)
glyphs.glyph.line_color = dict(field='district', transform=color_mapper)
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
#Creating drop down for state
options = list(set(crop_dt.index.get_level_values('State_Name').tolist()))
options.sort()
select = Select(title="State:", value="Tamil Nadu", options=options)
select.on_change('value', update_plot)
layout = row(widgetbox(slider, select), p)
curdoc().add_root(layout)
#Jasper Thanks a lot. This works, however it doesnt work with .loc[(['Tamil Nadu']), :]. Reason for having this is to filter the data by adding a bokeh dropdown or radio button object and refresh the plot based on the filters. The below code works only if .loc[(['Tamil Nadu']), :] is removed. Is there any other way to fix this please?
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Area.tolist(),
'y' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Production.tolist(),
'state' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('State_Name').tolist(),
'district' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').tolist()
}
source.data = new_data

Categories

Resources