onajourney
onajourney

Reputation: 33

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.

Upvotes: 2

Views: 3193

Answers (2)

onajourney
onajourney

Reputation: 33

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.

Upvotes: 1

Tony
Tony

Reputation: 8297

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:

enter image description here

Upvotes: 1

Related Questions