Bokeh: Update from multiple sliders with Button Click - python

Goal - I would like to slide the sliders around THEN when i'm ready to update press the button to update the values based upon where the current sliders are at.
Below is an example borrowed from Bokeh's website. Ideally, I would like to change the slider parameters, then when i'm ready for them all to update, click the button, have all the sliders update and display the changes. This process would be repeated over and over. I've tried the below but I'm not getting the desired result.
import numpy as np
from bokeh.io import curdoc,output_file, show
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput, Button
from bokeh.plotting import figure
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
button = Button(label="Update Changes", button_type="success")
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
def update():
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
button.on_click(update)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq, button)
curdoc().add_root(row(inputs, plot, width=800))

Delete the code that sets up the callbacks on slider change (because you don't want that) and call update_data from the button instead (after updating the callback function signature appropriately):
def update_data():
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
button.on_click(update_data)

Related

Dynamically change the coordinates and the text of annotation with slider in Bokeh plot

I have a Bokeh plot in which I have a slider. I want to change the coordinates of the line drawn with the slider, as shown in the screenshot of the figure. When I change the slider, the line changes its coordinates.
I tried using a slider widget with columndatasource. But, as I am new to Python, I cannot get to move the location and text of the label with the slider. Is there a way to do that?
My code is given below:
import math
import numpy as np
from bokeh.io import output_file
from bokeh.plotting import figure, show
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider, Label, LabelSet
from bokeh.plotting import ColumnDataSource, figure, show
from bokeh.models import Arrow, OpenHead, NormalHead, VeeHead
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
# line
x=[1,1]
y=[-1,1]
x1=[1,1]
y1=[1,1]
I want to introduce a variable which will change with the slider also, which, for now is 10 here.
sig_1 = 10*sig_x
then i introduced dictionaries, and along with x=x, y=y the x1=x1, y1=y1.
source = ColumnDataSource(data=dict(x=x, y=y))
fig = figure(title = 'Test of Text Rotation',
plot_height = 300, plot_width = 300,
x_range = (-3,3), y_range=(-3,3),
toolbar_location = None)
I could not find a way to add label to the line, so I added layout (from tutorial example). However, unlike fig.line command, the 'x' and 'y' cannot be added as variables (pardon me if i do not use the right jargon).
citation = Label(x=1, y=1, text = str(sig_1))
fig.line('x', 'y',source=source, line_width = 2) # Main Stress block
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code,
callback = CustomJS(args=dict(source=source ,val=amp_slider),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta)); // addition
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta)); // addition
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); // addition
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta)); // addition
source.change.emit();
""")
amp_slider.js_on_change('value', callback)
layout = row(fig, column(amp_slider),)
show(layout)
I added the lines of x1[0]=(1*Math.cos(theta))-(1*Math.sin(theta)), x1[1]=(1*Math.cos(theta))+(1*Math.sin(theta));, y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta)); and y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
This code, as anticipated does not move the label along with the line. Any explanation of what i am doing wrong, and the possibility of doing it will be very helpful.
You can pass the Lable to the CustomJS-callback as well and modify the values of this model like you do with the ColumnDataSource. Don't forget to call lable.change.emit();.
See the complete example below.
import numpy as np
from bokeh.plotting import figure, show, output_notebook
from bokeh.layouts import row
from bokeh.models import CustomJS, Slider, Label, ColumnDataSource
output_notebook()
theta = 0 #input the value here
theta = np.radians(-theta)
#Inputs to be made text boxes
sig_x = 10
source = ColumnDataSource(data=dict(x=[1,1], y=[-1,1]))
fig = figure(
title = 'Test of Text Rotation',
plot_height = 300,
plot_width = 300,
x_range = (-3,3),
y_range=(-3,3),
toolbar_location = None
)
fig.line('x', 'y',source=source, line_width = 2)
citation = Label(x=1, y=1, text = str(10*sig_x))
fig.add_layout(citation)
amp_slider = Slider(start=0, end=360, value=theta, step=1, title="theta")
# Adding callback code
callback = CustomJS(args=dict(source=source ,val=amp_slider, lable=citation),
code="""
const data = source.data;
var x = data['x'];
var y = data['y'];
var pi = Math.PI;
var theta = -1*(val.value) * (pi/180);
x[0]=(1*Math.cos(theta))-(1*Math.sin(theta));
x[1]=(1*Math.cos(theta))+(1*Math.sin(theta));
y[0]=(-1*Math.sin(theta))-(1*Math.cos(theta));
y[1]=(-1*Math.sin(theta))+(1*Math.cos(theta));
source.change.emit();
lable['x'] = x[1]
lable['y'] = y[1]
lable.change.emit();
"""
)
amp_slider.js_on_change('value', callback)
layout = row(fig, amp_slider)
show(layout)
Result
If you want to modify the text of the lable, you can use a similar approach.

Deploy Bokeh App to public without Python

I have a bokeh server app which is similar to the below code with call back functions and I have been trying to explore ways to deploy the bokeh app to any public server or html so that people even without python will be able to access the app.
Please let me know if there is a way to deploy the app to public
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Slider, TextInput
from bokeh.plotting import figure
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq)
curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"

How to pass the slider value in Bokeh back to Python code

I want to pass a slider value (that I've built with Bokeh) back to my Python code. The code generates 2 lines on a plot and allow me to alter the slope and intercept of one of them. But it fails when I introduce the callback javascript to pass the slider value as "ff" back into my Python code.
Can you help me with the callback syntax for getting the slider value back to python (eg see the print(ff) final line of the code) - I do want to do something more interesting than just print it out ultimately!
The error message from the callback is:
ValueError: expected an element of Dict(String, Instance(Model)), got {'my_dict': {'s': 0.5}}
My code is:-
from ipywidgets import interact
import numpy as np
from bokeh.io import push_notebook, show, output_notebook
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.models.callbacks import CustomJS
output_notebook()
x = np.linspace(0, 20, 200) # create equally spaced points.
s = 0.5 # slope.
i = 3 # intercept.
y = s * x + i # straight line.
my_dict = dict(s=s) # need to create a dict object to hold what gets passed in the callback.
callback = CustomJS(args=dict(my_dict=my_dict), code="""
var ff = cb_obj.value
my_dict.change.emit()
""")
// ff should be the slider value.
p = figure(title="simple line example", plot_height=300, plot_width=600, y_range=(-20,20),
background_fill_color='#efefef')
r = p.line(x, y, color="#8888cc", line_width=1.5, alpha=0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2*x+1.2, color="#0088cc", line_width=1.9, alpha=0.2) # 2nd line.
def update(w=s, a=i):
r.data_source.data['y'] = w * x + a # allow updates for the line r.
push_notebook()
show(p, notebook_handle=True)
interact(update, w=(-10,10), a=(-12,12) )
print(ff) # Return what the slider value is. I want ff accessible back in my python code.
I don't have Jupyter Notebook so these 2 examples are pure Bokeh apps, first one is using JS callback and the second one is using Python callback (Bokeh v1.0.4).
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, Slider, CustomJS, Range1d
from bokeh.layouts import column
slider_slope = Slider(start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(start = 0, end = 20, value = 10, step = 1)
slider_code = ''' i = slider_intercept.value
s = slider_slope.value
x = r.data_source.data['x'];
y = [];
for (index = 0; index < x.length; index ++)
y.push((s * x[index]) + i);
r.data_source.data['y'] = y
r.data_source.change.emit(); '''
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line.
slider_callback = CustomJS(args = dict(slider_slope = slider_slope,
slider_intercept = slider_intercept,
r = r), code = slider_code)
slider_slope.callback = slider_callback
slider_intercept.callback = slider_callback
layout = column(p, slider_slope, slider_intercept)
show(layout, notebook_handle = True)
You can easily translate it to Bokeh server app with Python callback:
import numpy as np
from bokeh.plotting import figure, show, curdoc
from bokeh.models import Slider, CustomJS
from bokeh.layouts import column
slider_slope = Slider(title = 'Slope', start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(title = 'Intercept', start = 0, end = 20, value = 10, step = 1)
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line.
def update(attr, old, new):
s = slider_slope.value # slope.
i = slider_intercept.value # intercept
x = r.data_source.data['x'];
y = []
for value in x:
y.append((s * value) + i)
r.data_source.data['y'] = y
slider_slope.on_change('value', update)
slider_intercept.on_change('value', update)
layout = column(p, slider_slope, slider_intercept)
curdoc().add_root(layout)
Result:
This is the solution. It creates a bokeh server app. It is run (from spyder) by using the file called: 20190328_start_bokeh_server.py
There is a straight line plotted and controlled by sliders. Clicking the button saves the slider values to a csv file.
To get the code below to run use this code (that's contained in 20190404_start_bokeh_server.py) in the console:
import os
os.chdir("C:\Users") # Change the working directory to be the script location.
os.system("start call bokeh serve --show 20190404_bokeh_server.py ") # Alternatively: This command can be typed into the anacondas prompt, once I've navigated to the directory holding the .py file.
"""
import pandas as pd
import numpy as np
from random import random
from numpy.random import randn
from bokeh.plotting import figure, show, curdoc
from bokeh.models import Slider, CustomJS, Range1d, Button
from bokeh.layouts import column
from bokeh.plotting import figure, curdoc
import os
slider_slope = Slider(title = 'Slope', start = 0, end = 1, value = 0.5, step = 0.1)
slider_intercept = Slider(title = 'Intercept', start = 0, end = 20, value = 10, step = 1)
s = slider_slope.value # slope.
i = slider_intercept.value # intercept.
x = np.linspace(-40, 20, 200)
y = [(s * xx + i) for xx in x]
p = figure(title = "simple line example", plot_height = 500, plot_width = 600, y_range = Range1d(start = -80, end = 40), background_fill_color = '#efefef')
r = p.line(x, y, color = "red", line_width = 1.5, alpha = 0.8) # 1st line. This line can be controlled by sliders.
q = p.line(x, 2 * x + 1.2, color = "blue", line_width = 1.9, alpha = 0.2) # 2nd line. This could be actuals.
def update(attr, old, new):
s = slider_slope.value # slope.
i = slider_intercept.value # intercept
x = r.data_source.data['x'];
y = []
for value in x:
y.append((s * value) + i)
r.data_source.data['y'] = y
# create a callback that will save the slider settings to a csv file when the button is clicked.
def callback():
os.chdir("C:\\Users") # Change the working directory to where I want to save the csv.
mydf = pd.DataFrame.from_dict({'slope':[0],'intercept':[0]}) # Create a DataFrame using pandas, based on a dictionary definition. Set the values to be 0 by default.
mydf.loc[0] = [slider_slope.value, slider_intercept.value] # Assign the first row to slope and intercept.
mydf.to_csv('slider.csv',index=True) # Write to the csv the final values of the button.
# add a button widget and configure with the call back
button = Button(label="Save slope and intercept to csv")
button.on_click(callback)
slider_slope.on_change('value', update)
slider_intercept.on_change('value', update)
layout = column(p, slider_slope, slider_intercept, button)
curdoc().add_root(layout)
show(layout, notebook_handle = True) # Launch the chart in the web browser.

How to find out if the app is being run in Bokeh or not?

In my Bokeh .py app, run by Bokeh server, I'm importing a module. In this module, a part of code depends on whether it is used in the Bokeh app or not (it can be used in "normal" Python script, too). How do I know whether the code is currently being used by Bokeh server or not?
You can check if a bokeh process is running with psutil. I have also made an example where show is used when a bokeh process is running (server) or show when no bokeh prcoess is running ("normal").
import psutil
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider, TextInput
from bokeh.plotting import figure, show
def checkIfProcessRunning(processName):
'''
Check if there is any running process that contains the given name processName.
'''
#Iterate over the all the running process
for proc in psutil.process_iter():
try:
# Check if process name contains the given name string.
if processName.lower() in proc.name().lower():
return True
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return False;
# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Set up plot
plot = figure(plot_height=400, plot_width=400, title="my sine wave",
tools="crosshair,pan,reset,save,wheel_zoom",
x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Set up widgets
text = TextInput(title="title", value='my sine wave')
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
# Set up callbacks
def update_title(attrname, old, new):
plot.title.text = text.value
text.on_change('value', update_title)
def update_data(attrname, old, new):
# Get the current slider values
a = amplitude.value
b = offset.value
w = phase.value
k = freq.value
# Generate the new curve
x = np.linspace(0, 4*np.pi, N)
y = a*np.sin(k*x + w) + b
source.data = dict(x=x, y=y)
for w in [offset, amplitude, phase, freq]:
w.on_change('value', update_data)
# Set up layouts and add to document
inputs = column(text, offset, amplitude, phase, freq)
if checkIfProcessRunning('bokeh'):
print('A bokeh process is running')
curdoc().add_root(row(inputs, plot, width=800))
curdoc().title = "Sliders"
else:
print('No bokeh process is running')
show(row(inputs, plot, width=800))

Bokeh: vbar not updating but circles do

I'm building a simple plot with bokeh 0.12.13 with a slider to dynamically update. Everything works nicely with circles but not with vbar and I do not understand why.
See code below
def update_title(attrname, old, new):
''' Update figure title when text box changes'''
plot.title.text = text.value
def update_data(attrname, old, new):
''' Update graph using a slider '''
# Get the current slider values
q = quantile.value
# Generate the new curve
X = np.array(range(len(sample_data[q].Index)))+0.35
Y = sample_data[q].Index_reported_Rate
source.data = dict(x=X, y=Y)
text = TextInput(title="title", value='Enter text')
quantile = Slider(start=0, end=5, value=1, step=1, title="quantile")
X = sample_data[0].Index.values
Y = sample_data[quantile.value].Index_reported_Rate.values
source = ColumnDataSource(data=dict(x=X, top=Y))
# Source below works for circles
# source_c = ColumnDataSource(data=dict(x=X, y=Y))
# Definition of the figure
plot = figure(plot_width=500,
plot_height=500,
x_axis_label=selected_factor,
x_range=sample_data[quantile.value].Index.values,
title="Figure"
)
plot.circle(x='x',
y='y',
source=source_c,
size=20
)
### Set up callbacks
text.on_change('value', update_title)
quantile.on_change('value', update_data)
### Set up layouts and add to document
inputs = widgetbox(text, quantile)
curdoc().add_root(row(inputs, plot, width=500))
curdoc().title = "Sliders"
So this code works perfectly fine (except I can't update the axes yet) but if I replace plot.circle with vbar as shown below, then it does not work.
By "not working", I mean that when I run the server, the figure displays the bars but values do not change when I slide the slider....
plot.vbar(x='x',
top='top',
source=source,
width=0.25,
bottom=0,
color='red'
)

Categories