Unable to refresh plot in Bokeh JS with jupyter notebook - python

I'm trying to port this example from Bokeh to inside a notebook (cf here the source code)
Here's my attempt:
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure
df = pandas.DataFrame({'A': [1.1, 2.7, 5.3], 'B': [2, 10, 9], 'C': [3.3, 5.4, 1.5], 'D': [4, 7, 15]},
index = ['a1', 'a2', 'a3'])
columns = sorted(df.columns)
def create_figure(x_v):
xs = df[x_v].values
ys = df[y.value].values
x_title = x_v.title()
y_title = y.value.title()
kw = dict()
kw['title'] = "%s vs %s" % (x_title, y_title)
p = figure(plot_height=600, plot_width=800, tools='pan,box_zoom,hover,reset', **kw)
p.xaxis.axis_label = x_title
p.yaxis.axis_label = y_title
sz = 9
c = "#31AADE"
p.circle(x=xs, y=ys, color=c, size=sz, line_color="white", alpha=0.6, hover_color='white', hover_alpha=0.5)
return p
fig = create_figure('A')
def update(x_value):
global fig
global layout
global bokeh_handle
fig = create_figure(x_value)
layout = row(controls, fig)
push_notebook(handle=bokeh_handle) #doesn't update?
x = Select(title='X-Axis', value='A', options=columns)
x.js_on_change('value', CustomJS(code="""
if (IPython.notebook.kernel !== undefined) {
var kernel = IPython.notebook.kernel;
cmd = "update('" + cb_obj.value + "')";
kernel.execute(cmd, {}, {});
}
"""))
controls = widgetbox([x], width=200)
layout = row(controls, fig)
bokeh_handle = show(layout, notebook_handle=True)
This display my plot of 'A vs B' but then the plot never updates when I change the select widget.
How can I make it work?
What I see is that the layout object is actually changed, because if I call show(layout) in a new cell it will redraw the updated layout.
However it seems that I'm not correctly updating the old layout with the new one, as the new once never changes.
How can I fix this?
thanks!

Related

How to use bokeh select element to make sliders hide or invisible?

This sample is altered from the bokeh example, sliders can control the bars in the figure. (sliders.py)
This sample is altered from the bokeh example, sliders can control the bars in the figure. (sliders.py)
In my situation, there are more than 30 sliders on the left. It seems a little messy, so I am trying to use bokeh select element to connect sliders. The aim is the slider area will show only one slider when I select.
I read the document, and there are two ways to use:
One is disabled. If True, the widget will be greyed-out and not responsive to UI events. But it would not hide. Another is visible, but I got an attribute error:
AttributeError("unexpected attribute 'visible' to Slider, similar attributes are disabled")
Is it possible to make bokeh sliders hide or invisible? Or is there any other way to make sliders (more than 30) distinguish more clearly?
Here is the code which can run in Jupyter notebook
import bokeh.plotting.figure as bk_figure
from bokeh.io import curdoc, show
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, Select
from bokeh.models.widgets import Slider, TextInput
from bokeh.io import output_notebook # enables plot interface in J notebook
import numpy as np
# init bokeh
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
output_notebook()
# Set up data
data = {
'line_x' : [1,2,3,4],
'line_y' : [4,3,2,1],
'bar_x':[1, 2, 3, 4],
'bar_bottom':[1,1,1,1],
'bar_top':[0.2, 2.5, 3.7, 4],
}
#bar_color
determine_top = data['bar_top']
determine_bottom = data['bar_bottom']
determine_color = []
for i in range(0,4):
if (determine_top[i] > determine_bottom[i]):
determine_color.append('#B3DE69') #green
else:
determine_color.append('firebrick')
i+=1
data['determine_colors'] = determine_color
source = ColumnDataSource(data=data)
# Set up plot
plot = bk_figure(plot_height=400, plot_width=400, title="test",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 10], y_range=[-5, 5])
plot.vbar(x='bar_x', width=0.5, top='bar_top',bottom='bar_bottom',
source=source, color='determine_colors')
# Set up widgets
select = Select(title="days_select:",
options=["d1_select", "d2_select", "d3_select", "d4_select"])
d1 = Slider(title="d1", value=0.0, start=-5.0, end=5.0, step=0.1)
d2 = Slider(title="d2", value=1.0, start=-5.0, end=5.0, step=0.1)
d3 = Slider(title="d3", value=0.0, start=0.0, end=2*np.pi)
d4 = Slider(title="d4", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_data(attrname, old, new):
# Get the current slider values
d1_value = d1.value
d2_value = d2.value
d3_value = d3.value
d4_value = d4.value
##select
#if select.value == "d2_select":
# d3.disabled = True
# d4.visible = False
# Generate the new curve
new_data = {
'line_x' : [1,2,3,4],
'line_y' : [4,3,2,1],
'bar_x': [1, 2, 3, 4],
'bar_bottom':[d1_value, d2_value, d3_value, d4_value],
'bar_top':[0.2, d1_value, d2_value, d3_value],
}
#bar_color
determine_top = new_data['bar_top']
determine_bottom = new_data['bar_bottom']
determine_color = []
for i in range(0,4):
if (determine_top[i] > determine_bottom[i]):
determine_color.append('green') #green
else:
determine_color.append('red')
i+=1
new_data['determine_colors'] = determine_color
source.data = new_data
for w in [select, d1, d2, d3, d4]:
w.on_change('value', update_data)
# Set up layouts and add to document
layout = row(widgetbox(select, d1, d2, d3, d4), plot)
def modify_doc(doc):
doc.add_root(row(layout, width=800))
doc.title = "Sliders"
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)
Thanks for any suggestions.

How do I update a plot type with RadioGroup buttons?

I have defined two kinds of plot functions: plt_percent and plt_number. I want to change between them using RadioGroup buttons.
Right now I do that in the update function by clearing the whole screen and starting from scratch:
curdoc().clear()
layout = row(menu, p)
curdoc().add_root(layout)
My dashboard has other plots too, but the update function only has to change this particular plot, without touching any other objects.
I suspect that might be possible with CustomJS but I don't know how.
Full code below:
import pandas as pd
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, RadioGroup, PrintfTickFormatter
colors=['navy', 'red']
data = {'fruits' : ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries'],
'2015' : [2, 1, 4, 3, 2, 4],
'2016' : [3, 2, 4, 4, 5, 3]}
df = pd.DataFrame(data)
def make_dataset(df):
df['total'] = df.sum(axis=1)
df['2015_%'] = round((df['2015'] * 100) / df.total)
df['2016_%'] = round((df['2016'] * 100) / df.total)
print(df)
return ColumnDataSource(df)
def plt_percent(src):
p = figure(x_range=src.data['fruits'],
plot_width=500, plot_height=500
)
p.vbar_stack(['2015_%', '2016_%'],
x='fruits',
width=0.5,
source=src ,
color=colors,
legend_label=['2015', '2016']
)
p.yaxis[0].formatter = PrintfTickFormatter(format='%0.0f%%')
return p
def plt_number(src):
p = figure(x_range=src.data['fruits'],
plot_width=500, plot_height=500
)
p.vbar_stack(['2015', '2016'],
x='fruits',
width=0.5,
source=src,
color=colors,
legend_label=['2015', '2016']
)
return p
def update(new):
count_type = menu.active
if count_type == 0:
p = plt_percent(source)
elif count_type == 1:
p = plt_number(source)
layout = row(menu, p)
curdoc().clear()
curdoc().add_root(layout)
# menu
menu = RadioGroup(labels=['percentage', 'number'], active = 0)
menu.on_click(update)
# initialise
source = make_dataset(df)
p = plt_percent(source)
layout = row(menu, p)
curdoc().add_root(layout)
Instead of creating a complete new page, just change the chart using the children attribute of layout (row/column) -
import pandas as pd
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, RadioGroup, PrintfTickFormatter
colors=['navy', 'red']
data = {'fruits' : ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries'],
'2015' : [2, 1, 4, 3, 2, 4],
'2016' : [3, 2, 4, 4, 5, 3]}
df = pd.DataFrame(data)
def make_dataset(df):
df['total'] = df.sum(axis=1)
df['2015_%'] = ((df['2015'] * 100) / df.total).round()
df['2016_%'] = ((df['2016'] * 100) / df.total).round()
print(df)
return df , ColumnDataSource(df)
def plt_percent(src):
p = figure(x_range=src.data['fruits'].tolist(), plot_width=500, plot_height=500)
p.vbar_stack(['2015_%', '2016_%'], x='fruits', width=0.5, source=src , color=colors, legend_label=['2015', '2016'])
p.yaxis[0].formatter = PrintfTickFormatter(format='%0.0f%%')
return p
def plt_number(src):
p = figure(x_range=src.data['fruits'].tolist(),plot_width=500, plot_height=500)
p.vbar_stack(['2015', '2016'], x='fruits', width=0.5, source=src, color=colors, legend_label=['2015', '2016'])
return p
def update(new):
count_type = menu.active
if count_type == 0:
p = plt_percent(source)
elif count_type == 1:
p = plt_number(source)
layout.children[1] = p
# menu
menu = RadioGroup(labels=['percentage', 'number'], active = 0)
menu.on_click(update)
# initialise
source = make_dataset(df)
p = plt_percent(source)
layout = row(menu, p)
curdoc().add_root(layout)
The better (and more seamless) will be if you just change the data behind the chart, but to do that you need to make chart from basic glyphs.

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

Bokeh widget-Working Checkbox Group Example

I am evaluating Bokeh to see if it is ready for more extensive use. I have plotted two columns of a dataframe (code at the end), "Close" and "Adj Close".
I want to put in checkboxes to toggle the display of both the line graphs in the plot. So if the relevant checkbox is unchecked the line does not appear. The Bokeh documentation at http://docs.bokeh.org/en/latest/docs/user_guide/interaction.html does talk about checkbox group but doesn't provide an explicit working example. I would appreciate any help in getting checkboxes working for columns of a dataframe.
import pandas as pd
from bokeh.plotting import figure, output_file, show
IBM = pd.read_csv(
"http://ichart.yahoo.com/table.csv?s=IBM&a=0&b=1&c=2011&d=0&e=1&f=2016",
parse_dates=['Date'])
output_file("datetime.html")
p = figure(width=500, height=250, x_axis_type="datetime")
p.line(IBM['Date'], IBM['Close'], color='navy', alpha=0.5)
p.line(IBM['Date'], IBM['Adj Close'], color='red', alpha=0.5)
show(p)
This is obviously a late reply but I'm currently trying to learn python and bokeh to hack out some sort of data dashboard. I was trying to figure out how the checkboxes worked and I stumbled on your question. This solution only works with bokeh serve . I don't know how to make it work in an HTML output.
I'm only modifying the line visibility and not the source. I didn't try it yet but I'm sure the legends would still show the invisible lines
Apologies for duct tape code.
#-| bokeh serve
#-|
import pandas as pd
from bokeh.io import curdoc,output_file, show
from bokeh.layouts import row, widgetbox
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import *
#Widgets
ticker = TextInput(title='Ticker Symbol',value='IBM')
button=Button(label='Lookup',button_type='success')
log = Paragraph(text="""log""",
width=200, height=100)
cb_group = CheckboxButtonGroup(labels=['Close', 'Adj Close'],active=[0,1])
cb_group.labels.append('Placebo')
#Plot
p = figure(title='',width=500, height=250, x_axis_type='datetime')
source = ColumnDataSource({'x': [], 'y1': [],'y2': []})
lineClose=p.line('x','y1',source=source, color='navy', alpha=0.5)
lineAdj=p.line('x','y2',source=source, color='red', alpha=0.5)
lines=[lineClose,lineAdj]
#Event handling
def error(msg):
log.text=msg
def update_data():
try:
src='http://ichart.yahoo.com/table.csv?s={symb}&a=0&b=1&c=2011&d=0&e=1&f=2016'.format(symb=ticker.value)
df=pd.read_csv(src,parse_dates=['Date'])
source.data=({'x': df['Date'], 'y1': df['Close'],'y2': df['Adj Close']})
except:
error('Error ticker')
def update_plot(new):
switch=cb_group.active
for x in range(0,len(lines)):
if x in switch:
lines[x].visible=True
else:
lines[x].visible=False
error('<CheckboxButtonGroup>.active = '+str(switch))
button.on_click(update_data)
cb_group.on_click(update_plot)
inputs=widgetbox(ticker,button,cb_group,log)
curdoc().add_root(row(inputs,p,width=800))
curdoc().title = 'Bokeh Checkbox Example'
button.clicks=1
I added the 'Placebo' checkbox to see if I could append to the checkbox group
instead of the typical method so I'm sure there's a way to more elegantly and dynamically add checkboxes.
I haven't been able to get the check boxes to work yet, although I wouldn't be surprised if that functionality is coming soon. In the meantime, here is a workaround using the multiselect widget:
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, MultiSelect
from bokeh.plotting import figure, output_file, show
import pandas as pd
IBM = pd.read_csv(
"http://ichart.yahoo.com/table.csv?s=IBM&a=0&b=1&c=2011&d=0&e=1&f=2016",
parse_dates=['Date'])
output_file("datetime.html")
source = ColumnDataSource({'x': IBM['Date'], 'y1': IBM['Close'], \
'y2': IBM['Adj Close'], 'y1p': IBM['Close'], 'y2p': IBM['Adj Close']})
p = figure(width=500, height=250, x_axis_type="datetime")
p.line('x', 'y1', source=source, color='navy', alpha=0.5)
p.line('x', 'y2', source=source, color='red', alpha=0.5)
callback = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var f = cb_obj.get('value')
y1 = data['y1']
y2 = data['y2']
y1p = data['y1p']
y2p = data['y2p']
if (f == "line2") {
for (i = 0; i < y1.length; i++) {
y1[i] = 'nan'
y2[i] = y2p[i]
}
} else if (f == "line1") {
for (i = 0; i < y2.length; i++) {
y1[i] = y1p[i]
y2[i] = 'nan'
}
} else if (f == "none") {
for (i = 0; i < y2.length; i++) {
y1[i] = 'nan'
y2[i] = 'nan'
}
} else {
for (i = 0; i < y2.length; i++) {
y1[i] = y1p[i]
y2[i] = y2p[i]
}
}
source.trigger('change');
""")
multi_select = MultiSelect(title="Lines to plot:", \
value=["line1", "line2", "none"], \
options=["line1", "line2", "none"], callback=callback)
layout = vform(multi_select, p)
show(layout)
The output looks like this:
For people still looking for this. It was solved by mosc9575. Here is a slightly modified version of mosc9575's solution code:
import numpy as np
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider, CheckboxGroup
from bokeh.plotting import ColumnDataSource, figure, show
# initial input data
x = np.linspace(0, 10, 500)
y = np.sin(x)
z = np.cos(x)
name_lst = ['sin', 'cos']
# dataframe
source = ColumnDataSource(data=dict(x=x, y=y, z=z))
# initialize figure
fig = figure()
line_renderer = [
fig.line('x', 'y', source=source, name=name_lst[0]),
fig.line('x', 'z', source=source, name=name_lst[1])
]
line_renderer[0].visible = False
# create a slider and a couple of check boxes
freq_slider = Slider(start=0.1, end=10, value=1, step=.1, title="Frequency")
checkbox = CheckboxGroup(
labels=name_lst,
active=[1, 1],
width=100
)
# callbacks
callback = CustomJS(args=dict(source=source, freq=freq_slider),
code="""
const data = source.data;
const k = freq.value;
const x = data['x'];
const y = data['y'];
const z = data['z'];
for (let i = 0; i < x.length; i++) {
y[i] = Math.sin(k*x[i]);
z[i] = Math.cos(k*x[i]);
}
source.change.emit();
""")
callback2 = CustomJS(args=dict(lines=line_renderer, checkbox=checkbox),
code="""
lines[0].visible = checkbox.active.includes(0);
lines[1].visible = checkbox.active.includes(1);
""")
# changes upon clicking and sliding
freq_slider.js_on_change('value', callback)
checkbox.js_on_change('active', callback2)
layout = row(
fig,
column(freq_slider, checkbox)
)
show(layout)
cos(kx) and a sin(kx) functions can be turned on and off using the checkboxes. With a slider k can be changed. This does not require a bokeh server.

Categories

Resources