Bokeh combine line and bar chart with hover - python

I want to create a combined bar and line chart with a hover tool. As I wanted to add a hover tool I initially created a figure and then tried to add the bars with vbar and the line with line_glyph.
This doesnt work, as it only creates a blank white canvas.
from bokeh.charts import Bar, output_file, show
from bokeh.plotting import figure
from bokeh.models.ranges import Range1d
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.models.glyphs import Line as Line_glyph
import pandas as pd
import numpy as np
data_2015_2016=pd.DataFrame({
'year':[2015,2015,2015,2015,2015],
'volume_neutral':[420,430,440,400,np.nan],
'volume_promo':[np.nan,np.nan,np.nan,np.nan,2000],
'volume_neutral_promo': [420,430,440,400,2000],
'Promo':['No','No','No','No','Yes'],
'Product':['Lemonade','Lemonade','Lemonade','Lemonade','Lemonade'],
'yw':['2015-W01','2015-W02','2015-W03','2015-W04','2015-W05']
})
hover=HoverTool(
tooltips=[
( 'Date', '#yw' ),
( 'Volume (in kg)', '#volume_neutral_promo' ), # use #{ } for field names with spaces
( 'Promo', '#Promo' ),
( 'Product', '#Product' )
])
p = figure(plot_width=1000, plot_height=800, tools=[hover],
title="Weekly Sales 2015-2016",toolbar_location="below")
source = ColumnDataSource(data=data_2015_2016)
#Bar Chart
#This worked however I donno how to combine it with a hoover
#p.Bar = Bar(data_2015_2016, label='yw', values='volume_promo', title="Sales",legend=False,plot_width=1000, plot_height=800)
p.vbar(x='yw', width=0.5, bottom=0,top='volume_promo', color="firebrick",source=source)
# create a line glyph object which references columns from source data
glyph = Line_glyph(x='yw', y='volume_neutral', line_color='green', line_width=2)
# add the glyph to the chart
p.add_glyph(source, glyph)
p.xaxis.axis_label = "Week and Year"
# change just some things about the y-axes
p.yaxis.axis_label = "Volume"
p.yaxis.major_label_orientation = "vertical"
p.y_range = Range1d(0, max(data_2015_2016.volume_neutral_promo))
output_file("bar_line.html")
show(p)

There are a few things wrong with your code:
You've mixed up some of your column names, e.g. volume_neutral_promo is what is actually in your data source, but instead you have the glyphs mistakenly reference volume_promo_neutral
If you want to use a categorical range with bokeh.plotting plots, you have to explicitly tell that:
p = figure(plot_width=1000, plot_height=800, tools=[hover],
title="Weekly Sales 2015-2016",toolbar_location="below",
# this part is new
x_range=['2015-W01','2015-W02','2015-W03','2015-W04','2015-W05'])
Here is your complete code updated. I have removed the parts about bokeh.charts as I would not recommend using that API anymore (it's basically unmaintained at this point). Also I used the simpler p.line instead of the low level Line glyph:
from numpy import nan
import pandas as pd
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure
data_2015_2016 = pd.DataFrame({
'year': [2015, 2015, 2015, 2015, 2015],
'volume_neutral': [420, 430, 440, 400, nan],
'volume_promo': [nan, nan, nan, nan, 2000],
'volume_neutral_promo': [420, 430, 440, 400, 2000],
'Promo': ['No', 'No', 'No', 'No', 'Yes'],
'Product': ['Lemonade', 'Lemonade', 'Lemonade', 'Lemonade', 'Lemonade'],
'yw': ['2015-W01', '2015-W02', '2015-W03', '2015-W04', '2015-W05']
})
source = ColumnDataSource(data=data_2015_2016)
hover=HoverTool(tooltips=[
( 'Date', '#yw' ),
( 'Volume (in kg)', '#volume_neutral_promo' ),
( 'Promo', '#Promo' ),
( 'Product', '#Product' ),
])
p = figure(plot_width=1000, plot_height=800, tools=[hover],
title="Weekly Sales 2015-2016",toolbar_location="below",
x_range=['2015-W01', '2015-W02', '2015-W03', '2015-W04', '2015-W05'])
p.vbar(x='yw', width=0.5, bottom=0, top='volume_promo', color="firebrick", source=source)
p.line(x='yw', y='volume_neutral', line_color='green', line_width=2, source=source)
p.xaxis.axis_label = "Week and Year"
p.yaxis.axis_label = "Volume"
p.yaxis.major_label_orientation = "vertical"
p.y_range.start = 0
p.y_range.range_padding = 0
output_file("bar_line.html")
show(p)
This results in the following plot with hover tool:

Related

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)

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

how to get the axis values on the HoverTool - Bokeh

I have written a code that reads from a .csv files and converts it into a pandas dataframe. I then proceed to plot the chart with candlesticks. The chart itself is good but when i try to use the HoverTools i cannot seem to add the axis values to the tool.
I used column data source but i wasnt able to understand it.
import pandas as pd
from math import pi
from bokeh.plotting import figure, show, output_file
from bokeh.models import HoverTool
df = pd.read_csv('/Users/robindhillon/Desktop/pyfiles/EURUSD.csv')
df.columns = ['date','open','high','low','close','volume']
df['date'] = pd.to_datetime([x[:-9] for x in
df['date'].squeeze().tolist()], dayfirst=True)
inc = df.close > df.open
dec = df.open > df.close
w = 86400000
hover = HoverTool(
tooltips=[
('date', '#date'),
('open', '#open' ),
('high', '#high' ),
('low', '#low' ),
('close', '#close'),
],
formatters={
'date' : 'datetime',
'open' : 'numeral',
'high' : 'numeral',
'low' : 'numeral',
'close': 'numeral',
},
mode='vline'
)
TOOLS = 'pan,wheel_zoom,box_zoom,reset,save,crosshair'
p = figure(x_axis_type = 'datetime', tools=TOOLS, plot_width=1200,
title='test')
p.xaxis.major_label_orientation = pi/4
p.grid.grid_line_alpha = 0.3
p.add_tools(hover)
p.segment(df.date, df.high, df.date, df.low, color="black")
p.vbar(df.date[inc], w, df.open[inc], df.close[inc],
fill_color="#12C98C", line_color="black")
p.vbar(df.date[dec], w, df.open[dec], df.close[dec],
fill_color="#F2583E", line_color="black")
output_file("candlestick.html", title="candlestick.py example")
show(p) # open a browser
Field names that begin with # are associated with columns in a ColumnDataSource. For instance the field name "#date" will display values from the "date" column whenever a hover is triggered. If the hover is for the 17th glyph, then the hover tooltip will correspondingly display the 17th date value. The hover tool won't work if you use field names that begin with # without a ColumnDataSource
import pandas as pd
from math import pi
from bokeh.plotting import figure, show, output_file
from bokeh.models import HoverTool, ColumnDataSource
df = pd.read_csv('/Users/robindhillon/Desktop/pyfiles/EURUSD.csv')
df.columns = ['date','open','high','low','close','volume']
df['date'] = pd.to_datetime([x[:-9] for x in
df['date'].squeeze().tolist()], dayfirst=True)
inc = df.close > df.open
dec = df.open > df.close
w = 86400000
hover = HoverTool(
tooltips=[
('date', '#date{%F}'),
('open', '#open' ),
('high', '#high' ),
('low', '#low' ),
('close', '#close'),
],
formatters={
'date' : 'datetime',
'open' : 'numeral',
'high' : 'numeral',
'low' : 'numeral',
'close': 'numeral',
},
mode='vline'
)
df['dateinc'] = df.date[inc]
df['openinc'] = df.open[inc]
df['closeinc'] = df.close[inc]
df['datedec'] = df.date[dec]
df['opendec'] = df.open[dec]
df['closedec'] = df.close[dec]
source = ColumnDataSource(df)
TOOLS = 'pan,wheel_zoom,box_zoom,reset,save,crosshair'
p = figure(x_axis_type = 'datetime', tools=TOOLS, plot_width=1200,
title='test')
p.xaxis.major_label_orientation = pi/4
p.grid.grid_line_alpha = 0.3
p.add_tools(hover)
p.segment('date', 'high', 'date', 'low', color="black", source=source)
p.vbar('dateinc', w, 'openinc', 'closeinc', fill_color="#12C98C", line_color="black", source=source)
p.vbar('datedec', w, 'opendec', 'closedec', fill_color="#F2583E", line_color="black", source=source)
output_file("candlestick.html", title="candlestick.py example")
show(p) # open a browser
In addition to the #field_name hover tool specification, there are also some "special variables" to display specific information not-related to the data source. To display the data-space value of the x-coordinate under the cursor, use $x

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)

Bokeh: DataTable - how to set selected rows

I would like to change the DataTable object row selection programmatically (without JS, just python). I have tried to so using the selected property of the underlying ColumnsSource with no success. How can this be done?
See an example app (needs bokeh serve to run) where pressing the button changes the selected rows, then updates both a table and plot. Is this all the functionality you need?
By the way you could just do it in JS and not need to use bokeh server, but if you have more python functionality going on then i guess you need it.
from datetime import date
from random import randint
from bokeh.io import output_file, show, curdoc
from bokeh.plotting import figure
from bokeh.layouts import widgetbox, row
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn,Button
output_file("data_table.html")
data = dict(
dates=[date(2014, 3, i+1) for i in range(10)],
downloads=[randint(0, 100) for i in range(10)],
)
def update():
#set inds to be selected
inds = [1,2,3,4]
source.selected = {'0d': {'glyph': None, 'indices': []},
'1d': {'indices': inds}, '2d': {}}
# set plot data
plot_dates = [data['dates'][i] for i in inds]
plot_downloads = [data['downloads'][i] for i in inds]
plot_source.data['dates'] = plot_dates
plot_source.data['downloads'] = plot_downloads
source = ColumnDataSource(data)
plot_source = ColumnDataSource({'dates':[],'downloads':[]})
table_button = Button(label="Press to set", button_type="success")
table_button.on_click(update)
columns = [
TableColumn(field="dates", title="Date", formatter=DateFormatter()),
TableColumn(field="downloads", title="Downloads"),
]
data_table = DataTable(source=source, columns=columns, width=400, height=280)
p = figure(plot_width=400, plot_height=400)
# add a circle renderer with a size, color, and alpha
p.circle('dates','downloads',source=plot_source, size=20, color="navy", alpha=0.5)
curdoc().add_root(row([table_button,data_table,p]))
You can select DataTable rows programmatically in python in this way:
source.selected.indices = [list of indices to be selected]
where source is the ColumnDataSource for the DataTable. If you have any callbacks for the source.selected here, remember to select the rows only after registering the callbacks so that they will get called.
Just for clarity you have to replace the source.selected property completely to trigger the changes. So the important line is:
source.selected = {'0d': {'glyph': None, 'indices': []},
'1d': {'indices': inds}, '2d': {}}
Individually setting the items in source.selected doesn't work
source.selected['1d']['indices'] = inds # Doesn't work

Categories

Resources