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.
Related
Having 3 line plots in Bokehjs, I would like Bokeh to show a fourth one, which is the sum of the other 3.
Example:
y1=[1,2,3,4,5]
y2=[4,5,6,7,8]
y3=[1,8,2,6,4]
Automatically generated plot would be:
y_all = [6,15,11,17,17]
Is there a way to accomplish this?
Maybe with a js callback?
I am not sure what you want, so I start with a very basic approche.
I assume you can use pandas. And your given DataFrame is this:
import pandas as pd
from bokeh.plotting import figure, show, output_notebook
output_notebook()
df = pd.DataFrame({
'y1':[1,2,3,4,5],
'y2':[4,5,6,7,8],
'y3':[1,8,2,6,4],
})
Static solution
With pandas.DataFrame.sum() you can create the sum and then you can use multi_line from bokeh.
df['y_all'] = df.sum(axis=1)
p = figure(width=300, height=300)
p.multi_line(
xs=[df.index]*4, ys=list(df.values.T), color=['red', 'green','blue', 'black']
)
show(p)
Interactive solution
Because you mentioned JS, I created an interactive solution. This solution is based on this post.
Here the sum is calculated on the fly by the selection given by the active CheckBoxes.
import pandas as pd
from bokeh.models import CheckboxGroup, CustomJS, ColumnDataSource
from bokeh.layouts import row
from bokeh.plotting import figure, show, output_notebook
output_notebook()
df = pd.DataFrame({
'y1':[1,2,3,4,5],
'y2':[4,5,6,7,8],
'y3':[1,8,2,6,4],
})
df['y_all'] = df.sum(axis=1)
source = ColumnDataSource(df)
colors = ['red', 'green','blue', 'black']
p = figure(width=300, height=300)
line_renderer = []
names = list(df.columns)
for name, color in zip(names, colors):
line_renderer.append(
p.line(
x = 'index',
y = name,
color=color,
name =name,
source=source
)
)
checkbox = CheckboxGroup(labels=names, active=list(range(len(names))), width=100)
callback = CustomJS(args=dict(lines=line_renderer,checkbox=checkbox, source=source),
code="""
const data = source.data;
for (let i = 0; i < data['y_all'].length; i++) {
data['y_all'][i] = 0
}
for(var j=0; j<lines.length; j++){
lines[j].visible = checkbox.active.includes(j);
}
console.log(data)
console.log(checkbox)
for(var item of checkbox.active){
let next_y = lines[item]["properties"]["name"]["spec"]["value"]
if (next_y != 'y_all'){
for (let i = 0; i < data[next_y].length; i++) {
data['y_all'][i] += data[next_y][i]
}
}
}
source.change.emit();
"""
)
checkbox.js_on_change('active', callback)
layout = row(p,checkbox)
show(layout)
I try to make a dashboard with bokeh but my hover tool got broken, since I tried to plot the data in a single diagram. I found some answers to this problem, but unfortunately I can't adapt it to my code.
I want to be able to turn off and on the visibility of data.
(Another problem is, that the whole plot vanishes, if you change the daterange to an area, where no values exist, maybe someone has a workaround for this. In an older version I tried to only change source emit, when range is on data, but this leads to other problems. Maybe someone has an idea for that too.)
Here is a reduced minimal working version of my code:
import pandas as pd
import numpy as np
from datetime import datetime
from bokeh.models import Button, CheckboxGroup, ColumnDataSource, CustomJS, DatetimeTickFormatter, HoverTool
from bokeh.models.widgets import DateRangeSlider
from bokeh.layouts import layout, column, row
from bokeh.palettes import Category20
from bokeh.plotting import figure, output_file, show
datesX = pd.date_range(start='1/1/2018', periods=100) #incl. '2018-01-01' to '2018-04-10'
numbof = 3
datesX = datesX[0:10].union(datesX[20:100])
valuesY = pd.DataFrame(np.random.randint(0,25,size=(90, numbof)), columns=list((f'V{i}' for i in range(numbof))))
movingY = pd.DataFrame(np.random.randint(0,10,size=(90, numbof)), columns=list((f'M{i}' for i in range(numbof))))
preCDS ={}
for i in range(numbof):
preCDS.update({f'x{i}': datesX})
preCDS.update({f'y{i}': valuesY[f'V{i}']})
preCDS.update({f'm{i}': movingY[f'M{i}']})
source = ColumnDataSource(preCDS)
source2 = ColumnDataSource(preCDS)
# output to static HTML file
output_file('file.html')
# create a new plot with a title and axis labels
p = figure(
title='file1', x_axis_label='Date', y_axis_label='yValue',
y_range=(0, 30), x_axis_type='datetime',
tools="pan, wheel_zoom, box_zoom, reset, save",
plot_width=1800, plot_height=480)
ypsilon = [f'y{i}' for i in range(numbof)]
em = [f'm{i}' for i in range(numbof)]
hover = HoverTool(line_policy='nearest', names=ypsilon + em,
tooltips=[('Timestamp', '#x{%Y-%m-%d %H:%M:%S}'), ('Wert', '#y')], ###what to do with #x and #y to make it iterable: x0, x1, x2, y0, ...
formatters={'#x': 'datetime'})
p.add_tools(hover)
date_range_slider = DateRangeSlider(
title="DateRange", start=datesX[0], end=datesX[89],
value=(datesX[0], datesX[89]), width=800, bar_color = (32,120,180))
checkbox_vals = CheckboxGroup(labels=[f'Plot {i+1}' for i in range(numbof)], active=[i for i in range(numbof)])
checkbox_mova = CheckboxGroup(labels=[f'Plot {i+1}' for i in range(numbof)], active=[i for i in range(numbof)])
vals=[]
mova=[]
for i in range(numbof):
vals.append(p.circle(x=f'x{i}', y=f'y{i}', source=source, color = Category20[20][2*i], size=3, name=f'y{i}'))
mova.append(p.line(x=f'x{i}', y=f'm{i}', source=source, line_color = Category20[20][2*i+1], line_width=2, name=f'm{i}'))
rangeCallback = CustomJS(args=dict(source=source, ref_source=source2, dRs=date_range_slider), code="""
// print out array of date from, date to
//console.log(dRs.value);
// dates returned from slider are not at round intervals and include time
const date_from = Date.parse(new Date(dRs.value[0]).toDateString());
const date_to = Date.parse(new Date(dRs.value[1]).toDateString());
//console.log(date_from, date_to)
// Creating the Data Sources
const data = source.data;
const ref = ref_source.data;
for (const [key, value] of Object.entries(data)) {
console.log(key)
// Creating new Array and appending correctly parsed dates
if(key.indexOf("x") == 0){
let new_ref = [];
let from_pos = [];
let to_pos = [];
ref[key].forEach(elem => {
elem = Date.parse(new Date(elem).toDateString());
new_ref.push(elem);
})
// Creating Indices with new Array
from_pos[key] = new_ref.indexOf(date_from);
to_pos[key] = new_ref.indexOf(date_to) + 1;
// re-create the source data from "reference"
const dataKeyM = key.replace('x', 'm');
const dataKeyY = key.replace('x', 'y');
data[dataKeyM] = ref[dataKeyM].slice(from_pos[key], to_pos[key]);
data[key] = ref[key].slice(from_pos[key], to_pos[key]);
data[dataKeyY] = ref[dataKeyY].slice(from_pos[key], to_pos[key]);
}
}
/*if (new_ref.includes(date_from) && new_ref.includes(date_to)) {
source.change.emit();
}*/
source.change.emit();
""")
checkboxValsCallback = CustomJS(args=dict(vals=vals, checkboxVals=checkbox_vals), code="""
var indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item)
return i;
}
return -1;
};
//vals.visible = indexOf.call(checkboxVals.active,0)>=0;
for (let i=0;i<vals.length;i++) {
vals[i].visible = indexOf.call(checkboxVals.active,i)>=0;
}
""")
checkboxMovaCallback = CustomJS(args=dict(mova=mova, checkboxMova=checkbox_mova), code="""
var indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item)
return i;
}
return -1;
};
//mova.visible = indexOf.call(checkboxMova.active,0)>=0;
for (let i=0;i<mova.length;i++) {
mova[i].visible = indexOf.call(checkboxMova.active,i)>=0;
}
""")
checkbox_vals.js_on_change('active', checkboxValsCallback)
checkbox_mova.js_on_change('active', checkboxMovaCallback)
date_range_slider.js_on_change('value', rangeCallback)
b1 = Button(label="select all", max_width = 300, button_type="primary")
b1.js_on_click(CustomJS(args=dict(s=checkbox_vals, t=checkbox_mova), code="""
s.active = [0,1,2]
t.active = [0,1,2]
"""))
b2 = Button(label="unselect all", max_width = 300)
b2.js_on_click(CustomJS(args=dict(s=checkbox_vals, t=checkbox_mova), code="""
s.active = []
t.active = []
"""))
layout = column(p, row(checkbox_vals, checkbox_mova, date_range_slider), row(b1, b2))
show(layout)
Thank You in advance.
For the tooltips: you specified x and y for the hover but when you look at the datasource of e.g. your line glyphs stored in mova you will see there is no x or y but many different fields. I changed the tooltips to x1 and y0 but I am not sure if this is wat you want. Just put a breakpoint somewhere and look e.g. in the mova[0].data_source.data and you will see all the available fields and choose the ones you want for your tooltips.
hover = HoverTool(line_policy='nearest', names=ypsilon + em,
tooltips=[('Timestamp', '#x1{%Y-%m-%d %H:%M:%S}'), ('Wert', '#y0')],
formatters={'#x1': 'datetime'})
Can someone explain, why this doesn't do anything and how I can print values from inside the callback function? Problem must lay in the callback function. I want my plot to be updated by the date range slider in a standalone html file, but nothing happens, when I change the values of the slider.
import numpy as np
import pandas as pd
from datetime import datetime
from bokeh.models import ColumnDataSource, DatetimeTickFormatter, HoverTool
from bokeh.models.widgets import DateRangeSlider
from bokeh.layouts import layout, column
from bokeh.models.callbacks import CustomJS
from bokeh.plotting import figure, output_file, show, save
datesX = pd.date_range(start='2018-01-02', periods=100)
# valuesY = pd.DataFrame(np.random.randint(0,25,size=(100, 1)), columns=list('A'))
np.random.seed(0)
valuesY = np.random.rand(100)
source = ColumnDataSource(data={'x': datesX, 'y': valuesY})
# output to static HTML file
output_file('file.html')
hover = HoverTool(tooltips=[('Timestamp', '#x{%Y-%m-%d %H:%M:%S}'), ('Value', '#y')], formatters={'#x': 'datetime'})
date_range_slider = DateRangeSlider(title="Zeitrahmen", start=datesX[0], end=datesX[99], \
value=(datesX[0], datesX[99]), step=1, width=1000)
# create a new plot with a title and axis labels
p = figure(title='file1', x_axis_label='Date', y_axis_label='yValue', x_axis_type='datetime',
tools="pan, wheel_zoom, box_zoom, reset", plot_width=1000, plot_height=700)
# add a line renderer with legend and line thickness
p.line(x='x', y='y', source=source, line_width=2)
p.add_tools(hover)
callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
var a = cb_obj.value;
var xmin = a[0];
var xmax = a[1];
var x = data['x'];
var y = data['y'];
for (var i = 0; i < x.length; i++) {
if (data['x'][i] == xmin){
startidx = i;
}
if (data['x'][i] == xmax){
endidx = i;
}
}
int j=0;
while(j <= endidx-startidx){
x[j] = data['x'][startidx+j];
y[j] = data['y'][startidx+j];
j++;
}
source.change.emit();
""")
date_range_slider.js_on_change('value', callback)
layout = column(p, date_range_slider)
# show the results
show(layout)
When developing JS callbacks, it is essential to learn how to view your browser's JavaScript console. This differs by browser so you will have to search how to access the console on whatever specific browser you are using.
Once you can see the console, you will see that your code has an error:
SyntaxError: Unexpected identifier 'j'
because you have int j instead og var j in your callback code. It that is fixed, there is then another error:
ReferenceError: Can't find variable: endidx
It's not really clear what you you actually want to happen, so it's not really possible to try to actually fix the code completely, but hopefully this gives you the information you need to properly do the debugging yourself. Some other tips for debugging:
to "print" in console, use console.log(...)
you can also add the command debugger in the callback code. If you run the Bokeh script with BOKEH_MINIFIED=no and the browser dev tools window open, then the exectution will stop at that point and you can single step through the code.
I wanted to create a plot with bokeh in python which runs quite well so far. But now I wanted to add a Slider and tell him to hide all bars in my vbar plot which are lower than the value of the slider.
current = df[(df['ID'] > num_tokens.value[0])].dropna()
source.data = {
'ID': current.ID
}
I tried to create a variable 'current' and assign it to the 'ID' column so that the plot can update the plot. But I always get a TypeError: Int is not subscriptable. How can I make my slider widget make work?
Thank you in advance
Don't know if we must close this issue or not but I would sugget using a customJS callback:
Create initially a source and a render_soruce from df
source = ColumnDataSource(df)
renderer_source = ColumnDataSource(df)
Then define your callback and your slider
code = """
var slider_value= cb_obj.value; //cb_obj is your slider widget
var data=source.data;
var data_id = data['ID'];
var data_x=data['x'];
var data_y=data['y'];
var render_x=render['x'];
var render_y=render['y'];
var x = [];
var y = [];
render_x=[];
render_y=[];
for (var i=0;i<data_id.length; i++){
if (data_id[i]== slider_valuer) {
x.push(data_x[i]);
y.push(data_y[i]);
}
renderer_source.data['x']=x;
renderer_source.data['y']=y;
renderer_source.change.emit();
"""
callback = CustomJS(args=dict(source=source, renderer_source=renderer_source), code=code)
slider = Slider(start=0, end=(max_value_o_slider), value=1, step=1, title="Frame")
slider.js_on_change('value', callback)
And identify source=renderer_source in your plot
You can achieve this with almost no JavaScript using a view:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CDSView, CustomJSFilter, Slider, CustomJS
from bokeh.plotting import figure
N = 5
x = list(range(1, N + 1))
top = list(range(1, N + 1))
# Specifying manual ranges to prevent range changes when some bars are hidden.
p = figure(x_range=(0, N + 1), y_range=(0, N + 1))
ds = ColumnDataSource(data=dict(x=x, top=top))
s = Slider(start=0, end=N, value=0)
# Making sure that everything that depends on the `ds`
# is notified when the slider value is changed.
s.js_on_change('value', CustomJS(args=dict(ds=ds),
code="ds.change.emit();"))
view = CDSView(source=ds,
filters=[CustomJSFilter(args=dict(ds=ds, slider=s),
code="return ds.data['top'].map((top) => top > slider.value);")])
p.vbar(x='x', width=0.7, top='top', bottom=0, source=ds, view=view)
curdoc().add_root(column(p, s))
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