I have a script to plot the prices of some share that the user wants to look at : he can choose the shares via a Dropdown button and Bokeh will draw the curve accordingly.
(I am working in jupyter notebook) :
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
output_notebook()
my code is the following :
from bokeh.models import Callback, ColumnDataSource, Select,CustomJS
from bokeh.plotting import figure, show, gridplot
from bokeh.models.widgets.layouts import VBox
import pandas as pd
shares = ['AAPL', 'MSFT', 'IBM', 'All']
AAPL = pd.read_csv("http://ichart.yahoo.com/table.csv?s=AAPL&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
MSFT = pd.read_csv("http://ichart.yahoo.com/table.csv?s=MSFT&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
IBM = pd.read_csv("http://ichart.yahoo.com/table.csv?s=IBM&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
max_price = max(AAPL['Adj Close'].max(), MSFT['Adj Close'].max(), IBM['Adj Close'].max()) + 10
min_date = min(AAPL['Date'].min(), MSFT['Date'].min(), IBM['Date'].min())
max_date = max(AAPL['Date'].max(), MSFT['Date'].max(), IBM['Date'].max())
myplot = figure(title="Share price", x_axis_type="datetime", x_range=[min_date,max_date],y_range=[0,max_price],
background_fill='#FFF5EE', plot_width=900, plot_height = 400, outline_line_color= None)
source_AAPL = ColumnDataSource(data=dict(x=AAPL['Date'], y = AAPL['Adj Close'], ytemp = AAPL['Adj Close']))
source_MSFT = ColumnDataSource(data=dict(x=MSFT['Date'], y = MSFT['Adj Close'], ytemp = MSFT['Adj Close']))
source_IBM = ColumnDataSource(data=dict(x=IBM['Date'], y = IBM['Adj Close'], ytemp = IBM['Adj Close']))
myplot.line(x ='x', y ='y', color='#A6CEE3', source = source_AAPL, name='AAPL')
myplot.line(x ='x', y ='y', color='#33A02C', source = source_MSFT, name='IBM')
myplot.line(x ='x', y ='y', color='#FB9A99', source = source_IBM, name='MSFT')
Callback_Shares = CustomJS(args={'source_AAPL': source_AAPL,'source_MSFT': source_MSFT,'source_IBM': source_IBM}, code="""
var f = cb_obj.get('value');
var data_AAPL = source_AAPL.get('data');
var data_MSFT = source_MSFT.get('data');
var data_IBM = source_IBM.get('data');
if (f == 'AAPL') {
data_MSFT['y'] = [0 for i in range(len(data_MSFT['x']))];
data_IBM['y'] = [0 for i in range(len(data_IBM['x']))];
data_AAPL['y'] = data_AAPL['ytemp'] ;
source_AAPL.trigger('change');
source_MSFT.trigger('change');
source_IBM.trigger('change');
}
if (f == 'MSFT') {
data_AAPL['y'] = [0 for i in range(len(data_AAPL['x']))];
data_IBM['y'] = [0 for i in range(len(data_IBM['x']))];
data_MSFT['y'] = data_MSFT['ytemp'] ;
source_AAPL.trigger('change');
source_MSFT.trigger('change');
source_IBM.trigger('change');
}
if (f == 'IBM') {
data_AAPL['y'] = [0 for i in range(len(data_AAPL['x']))];
data_MSFT['y'] = [0 for i in range(len(data_MSFT['x']))];
data_IBM['y'] = data_IBM['ytemp'] ;
source_AAPL.trigger('change');
source_MSFT.trigger('change');
source_IBM.trigger('change');
}
if (f == 'All') {
data_AAPL['y'] = data_AAPL['ytemp'];
data_MSFT['y'] = data_MSFT['ytemp'];
data_IBM['y'] = data_IBM['ytemp'];
source_AAPL.trigger('change');
source_MSFT.trigger('change');
source_IBM.trigger('change');
}"""
)
dropdown = Select(title="Shares:", value=shares[3], options=shares, callback = Callback_Shares)
myfigure = VBox(dropdown, gridplot([[myplot]]))
show(myfigure)
My problem is the figure always shows the 3 curves and does not take into account the choice of the DropDown...
The other answer is unfortunately not an optimal one. As a project maintainer I feel obligated to show the project in the best light. Here is a much simpler complete example that functions the same and works with Bokeh 0.12.4:
from bokeh.models import CustomJS, ColumnDataSource, Select
from bokeh.plotting import figure, output_file, show
from bokeh.layouts import column
import pandas as pd
url = "http://ichart.yahoo.com/table.csv?s=%s&a=0&b=1&c=2000&d=0&e=1&f=2015"
AAPL = pd.read_csv(url % "AAPL", parse_dates=['Date'])
MSFT = pd.read_csv(url % "MSFT", parse_dates=['Date'])
IBM = pd.read_csv(url % "IBM", parse_dates=['Date'])
max_price = max(AAPL['Close'].max(), MSFT['Close'].max(), IBM['Close'].max())
source = ColumnDataSource({
'xAAPL' : AAPL['Date'], 'yAAPL' : AAPL['Close'], 'yAAPLp' : AAPL['Close'],
'xMSFT' : MSFT['Date'], 'yMSFT' : MSFT['Close'], 'yMSFTp' : MSFT['Close'],
'xIBM' : IBM['Date'], 'yIBM' : IBM['Close'], 'yIBMp' : IBM['Close']
})
p = figure(width=500, height=250, x_axis_type="datetime", y_range=[0, max_price+10])
r_aapl = p.line('xAAPL', 'yAAPL', source=source, color='navy', alpha=0.5)
r_msft = p.line('xMSFT', 'yMSFT', source=source, color='red', alpha=0.5)
r_ibm = p.line('xIBM', 'yIBM', source=source, color='green', alpha=0.5)
callback = CustomJS(args=dict(r_aapl=r_aapl, r_msft=r_msft, r_ibm=r_ibm), code="""
f = cb_obj.value;
r_aapl.visible = false;
r_msft.visible = false;
r_ibm.visible = false;
if (f == "AAPL") { r_aapl.visible = true; }
else if (f == "MSFT") { r_msft.visible = true; }
else if (f == "IBM") { r_ibm.visible = true; }
else {
r_aapl.visible = true;
r_msft.visible = true;
r_ibm.visible = true;
}
""")
shares = ['AAPL', 'MSFT', 'IBM', 'All']
multi_select = Select(title="Select Shares:", value=shares[3], options=shares, callback=callback)
output_file("datetime.html")
show(column(multi_select, p))
I should also add, "interactive legends" that allow glyphs to be hidden or muted by clicking on the legend, will be added as as standard feature in 0.12.5.
Rather than updating actual data sources, there are much easier ways to control visibility. You can set a glyph's alpha to zero, or color to None, or probably most directly, set its renderer's visible property to False as seen in this example. The relevant part of that example, simplified some, is this code:
r0 = p.line(x, y0, color="red")
r1 = p.line(x, y1, color="green")
r2 = p.line(x, y2, color="blue")
checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"],
active=[0, 1, 2], width=100)
checkbox.callback = CustomJS.from_coffeescript(
args=dict(r0=r0, r1=r1, r2=r2, checkbox=checkbox),
code="""
r0.visible = 0 in checkbox.active;
r1.visible = 1 in checkbox.active;
r2.visible = 2 in checkbox.active;
""")
I found the following solution after quite some work :
from bokeh.models import CustomJS, ColumnDataSource, Select
from bokeh.plotting import figure, output_file, show
from bokeh.models.layouts import VBox
import pandas as pd
AAPL = pd.read_csv("http://ichart.yahoo.com/table.csv?s=AAPL&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
MSFT = pd.read_csv("http://ichart.yahoo.com/table.csv?s=MSFT&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
IBM = pd.read_csv("http://ichart.yahoo.com/table.csv?s=IBM&a=0&b=1&c=2000&d=0&e=1&f=2015",parse_dates=['Date'])
max_price = max(AAPL['Close'].max(), MSFT['Close'].max(), IBM['Close'].max()) + 10
min_date = min(AAPL['Date'].min(), MSFT['Date'].min(), IBM['Date'].min())
max_date = max(AAPL['Date'].max(), MSFT['Date'].max(), IBM['Date'].max())
output_file("datetime.html")
source = ColumnDataSource({'xAAPL': AAPL['Date'], 'xMSFT': MSFT['Date'], 'xIBM': IBM['Date'],
'yAAPL': AAPL['Close'], 'yAAPLp': AAPL['Close'], \
'yMSFT': MSFT['Close'], 'yMSFTp': MSFT['Close'], \
'yIBM': IBM['Close'], 'yIBMp': IBM['Close'] })
p = figure(width=500, height=250, x_axis_type="datetime", x_range=[min_date,max_date],y_range=[0,max_price])
p.line('xAAPL', 'yAAPL', source=source, color='navy', alpha=0.5)
p.line('xMSFT', 'yMSFT', source=source, color='red', alpha=0.5)
p.line('xIBM', 'yIBM', source=source, color='green', alpha=0.5)
callback = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var f = cb_obj.get('value')
yAAPL = data['yAAPL']
yMSFT = data['yMSFT']
yIBM = data['yIBM']
yAAPLp = data['yAAPLp']
yMSFTp = data['yMSFTp']
yIBMp = data['yIBMp']
if (f == "AAPL") {
for (i = 0; i < yAAPL.length; i++) {yAAPL[i] = yAAPLp[i]}
for (i = 0; i < yMSFT.length; i++) {yMSFT[i] = 'nan'}
for (i = 0; i < yIBM.length; i++) {yIBM[i] = 'nan'}
}
else if (f == "MSFT") {
for (i = 0; i < yAAPL.length; i++) {yAAPL[i] = 'nan'}
for (i = 0; i < yMSFT.length; i++) {yMSFT[i] = yMSFTp[i]}
for (i = 0; i < yIBM.length; i++) {yIBM[i] = 'nan'}
}
else if (f == "IBM") {
for (i = 0; i < yAAPL.length; i++) {yAAPL[i] = 'nan'}
for (i = 0; i < yMSFT.length; i++) {yMSFT[i] = 'nan'}
for (i = 0; i < yIBM.length; i++) {yIBM[i] = yIBMp[i]}
}
else {
for (i = 0; i < yAAPL.length; i++) {yAAPL[i] = yAAPLp[i]}
for (i = 0; i < yMSFT.length; i++) {yMSFT[i] = yMSFTp[i]}
for (i = 0; i < yIBM.length; i++) {yIBM[i] = yIBMp[i]}
}
source.trigger('change');
""")
shares = ['AAPL', 'MSFT', 'IBM', 'All']
multi_select = Select(title="Select Shares:", value=shares[3], options=shares, callback=callback)
layout = VBox(multi_select, p)
show(layout)
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)
Let say I have 2 different lines:
df[0]=fig.line(x = 'x', y = 'y',..., name = 'toto0',source=s0)
df[1]=fig.line(x = 'x', y = 'y',..., name = 'toto1',source=s1)
If I want to have to possibility to hide them, I use this piece of code:
checkbox = CheckboxGroup(labels=['toto0','toto1'], active=2, width=100)
callback = CustomJS(args=dict(l0=df[0],l1=df[1],checkbox=checkbox),
code="""
l0.visible = 0 in checkbox.active;
l1.visible = 1 in checkbox.active;
""")
checkbox.js_on_change('active', callback)
layout = row(fig,checkbox)
show(layout)
Now let's say I have 20 different lines.
How to proceed to compact the code below ?
callback = CustomJS(args=dict(l0=df[0],...,l19=df[19],checkbox=checkbox),
code="""
l0.visible = 0 in checkbox.active;
l1.visible = 1 in checkbox.active;
...
l19.visible = 19 in checkbox.active;
""")
This is a Python and a JavaScript question ... thanks !
The main idea is to collect all line renderes in a list and pass this list to the CustomJS. There you can loop over this list again and apply your changes.
Minimal Example
import pandas as pd
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import CheckboxGroup, CustomJS
from bokeh.layouts import row
output_notebook()
df = pd.DataFrame(
{'x':range(5),
'red':range(5),
'blue':list(range(5))[::-1],
'green':[2]*5}
)
fig = figure(width=300, height=300)
line_renderer = []
names = list(df.columns[1:])
for name in names:
line_renderer.append(
fig.line(
x = 'x',
y = name,
color=name,
name =name,
source=df
)
)
checkbox = CheckboxGroup(labels=names, active=list(range(len(names))), width=100)
callback = CustomJS(args=dict(lines=line_renderer,checkbox=checkbox),
code="""
for(var i=0; i<lines.length; i++){
lines[i].visible = checkbox.active.includes(i);
}
"""
)
checkbox.js_on_change('active', callback)
layout = row(fig,checkbox)
show(layout)
Output
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'})
I new in using bokeh.
This is what I am doing. From osmnx I get data of schools and hospitals in Haiti.
Without writing all the code I arrive to get the following
data1=dict(
x=list(schools['x'].values),
y=list(schools['y'].values)
)
data2=dict(
x=list(hospitals['x'].values),
y=list(hospitals['y'].values)
)
building = 'Schools'
buildings = {
'Schools': {
'title': 'Schools',
'data': data1,
'color': 'black'
},
'Hospitals': {
'title': 'Hospitals',
'data': data2,
'color': 'red'
}
}
building_select = Select(value=building, title='Building', options=sorted(buildings.keys()))
I would like to change the visualisation between schools and hospitals by selecting it. I define the function that change the data to take and the color.
def returnInfo(building):
dataPoints = buildings[building]['data']
color = buildings[building]['color']
return dataPoints, color
dataPoints, color = returnInfo(building)
I define the function make_plot
def make_plot(dataPoints, title, color):
TOOLS = "pan, wheel_zoom, reset,save"
p = figure(plot_width=800,
tools=TOOLS,
x_axis_location=None,
y_axis_location=None)
# Add points on top (as black points)
buildings = p.circle('x', 'y', size=4, source=data1, color=color)
hover_buildings = HoverTool(renderers = [buildings], point_policy="follow_mouse", tooltips = [("(Long, Lat)", "($x, $y)")])
p.add_tools(hover_buildings)
return p
plot = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
then I update
def update_plot(attrname, old, new):
building = building_select.value
p.title.text = "Data for " + buildings[building]['title']
src = buildings[building]['data']
dataPoints, color = returnInfo(building)
dataPoints.update
building_select.on_change('value', update_plot)
controls = column(building_select)
curdoc().add_root(row(plot, controls))
but it does not work: i.e. I am not able to change the points from schools to hospitals even if I have the cursor. Where is the error in the update section?
As first solution I suggest to use legend.click_plolicy = 'hide' to toggle visibility of your buildings on the map (Bokeh v1.1.0)
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.tile_providers import CARTODBPOSITRON_RETINA
import osmnx as ox
amenities = ['hospital', 'school']
for i, amenity in enumerate(amenities):
buildings = ox.pois_from_address("Port-au-Prince, Haiti", amenities = [amenity], distance = 3500)[['geometry', 'name', 'element_type']]
for item in ['way', 'relation']:
buildings.loc[buildings.element_type == item, 'geometry'] = buildings[buildings.element_type == item]['geometry'].map(lambda x: x.centroid)
buildings.name.fillna('Hospital' if i == 0 else 'School', inplace = True)
amenities[i] = buildings.to_crs(epsg = 3857)
p = figure(title = "Port-au-Prince, Haiti", tools = "pan,wheel_zoom,hover,reset", x_range = (-8057000, -8048500), y_range = (2098000, 2106000),
tooltips = [('Name', '#name'), ("(Long, Lat)", "($x, $y)")], x_axis_location = None, y_axis_location = None, active_scroll = 'wheel_zoom')
p.add_tile(CARTODBPOSITRON_RETINA)
p.grid.grid_line_color = None
for i, b in enumerate(amenities):
source = ColumnDataSource(data = dict(x = b.geometry.x, y = b.geometry.y, name = b.name.values))
p.circle('x', 'y', color = 'red' if i == 0 else 'blue', source = source, legend = 'Hospital' if i == 0 else 'School')
p.legend.click_policy = 'hide'
show(p)
And if you want the Select widget then here is another alternative (Bokeh v1.1.0):
from bokeh.models import ColumnDataSource, Column, Select, CustomJS
from bokeh.plotting import figure, show
from bokeh.tile_providers import CARTODBPOSITRON_RETINA
import osmnx as ox
amenities = ['hospital', 'school']
for i, amenity in enumerate(amenities):
buildings = ox.pois_from_address("Port-au-Prince, Haiti", amenities = [amenity], distance = 3500)[['geometry', 'name', 'element_type']]
for item in ['way', 'relation']:
buildings.loc[buildings.element_type == item, 'geometry'] = buildings[buildings.element_type == item]['geometry'].map(lambda x: x.centroid)
buildings.name.fillna('Hospital' if i == 0 else 'School', inplace = True)
buildings = buildings.to_crs(epsg = 3857)
amenities[i] = dict(x = list(buildings.geometry.x), y = list(buildings.geometry.y), name = list(buildings.name.values), color = (['red'] if i == 0 else ['blue']) * len(buildings.name.values))
source = ColumnDataSource(amenities[0])
p = figure(title = "Hospitals", tools = "pan,wheel_zoom,hover,reset", x_range = (-8057000, -8048500), y_range = (2098000, 2106000),
tooltips = [('Name', '#name'), ("(Long, Lat)", "($x, $y)")], x_axis_location = None, y_axis_location = None, active_scroll = 'wheel_zoom')
p.add_tile(CARTODBPOSITRON_RETINA)
p.circle(x = 'x', y = 'y', color = 'color', source = source)
p.grid.grid_line_color = None
code = ''' source.data = (cb_obj.value == 'Hospitals' ? data[0] : data[1]); p.title.text = cb_obj.value; '''
select = Select(options = ['Hospitals', 'Schools'], callback = CustomJS(args=dict(p = p, source = source, data = amenities), code = code))
show(Column(p, select))
Let me know if you need any explanation on this code.
Below are the changes required to make your code work:
In your make_plot method, since you want to update the title of the plot on selection change, replace
p = figure(plot_width=800,
tools=TOOLS,
x_axis_location=None,
y_axis_location=None)
with
p = figure(plot_width=800,
tools=TOOLS,
title=title,
x_axis_location=None,
y_axis_location=None)
Also, since you want to update the data and color of the buildings, return the buildings too in the method, so that the complete method now looks like:
def make_plot(dataPoints, title, color):
TOOLS = "pan, wheel_zoom, reset,save"
p = figure(plot_width=800,
tools=TOOLS,
title=title,
x_axis_location=None,
y_axis_location=None)
# Add points on top (as black points)
buildings = p.circle('x', 'y', size=4, source=data1, color=color)
hover_buildings = HoverTool(renderers = [buildings], point_policy="follow_mouse", tooltips = [("(Long, Lat)", "($x, $y)")])
p.add_tools(hover_buildings)
return p, buildings
Next, instead of the call to
plot = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
you need to get the returned buildings also in a variable so that it can be directly updated. So now your call will look like
plot, b = make_plot(dataPoints, "Data for " + buildings[building]['title'], color)
Finally, change your update_plot method, so that it looks like this:
def update_plot(attrname, old, new):
building = building_select.value
plot.title.text = "Data for " + buildings[building]['title']
src = buildings[building]['data']
dataPoints, color = returnInfo(building)
b.data_source.data = dataPoints
b.glyph.fill_color = color
With these changes, it would work as expected. See the results attached.
Sample data used is:
data1=dict(
x=[1,2,3],
y=[2,1,3]
)
data2=dict(
x=[1,2,3],
y=[1,3,2]
)
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.