0

I try to implement several sliders and use an own function, foo, to calculate new values to update the plot. I tried to modify the example from the documentation, but without success; the page loads, I can move the sliders but the plot does not update (please note that I want to use a function I cannot define in javascript; I now use foo just for illustration purposes).

There must be an issue with the callback (see entire code below):

def callback(source=source):
    data = source.data
    a_dynamic = cb_obj.a_slider.value  # cb_obj.get('a_slider')
    b_dynamic = cb_obj.b_slider.value
    x, y = data['x'], data['y']

    y = foo(x, a_dynamic, b_dynamic)

    source.change.emit()

I don't know how

1) to access the slider values correctly (using cb_obj.<slider_id>.value, or cb_obj.get(<slider_id>) or something completely else?)

2) to give the slider an actual ID; the title argument is only the text that appears above the slider but is probably not its ID and using the id argument does not work the way I use it.

How would I do this correctly?

import numpy as np
from bokeh.layouts import row, widgetbox
from bokeh.models import CustomJS, Slider
from bokeh.plotting import figure, output_file, show, ColumnDataSource
import bokeh

bokeh.io.reset_output()


def foo(xval, a, b):
    return np.power(xval, a) + b


# some artificial data
a0 = 2.
b0 = 1.
x = np.linspace(-100., 100, 1000)
y = foo(x, a0, b0)


source = ColumnDataSource(data=dict(x=x, y=y))

plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)


def callback(source=source):
    data = source.data
    a_dynamic = cb_obj.a_slider.value  # cb_obj.get('a_slider')
    b_dynamic = cb_obj.b_slider.value
    x, y = data['x'], data['y']

    y = foo(x, a_dynamic, b_dynamic)

    source.change.emit()


a_slider_obj = Slider(start=0, end=3, value=a0, step=0.1, id='a_slider',
                      title="a_slider", callback=CustomJS.from_py_func(callback))
# callback.args["a_slider"] = a_slider_obj

b_slider_obj = Slider(start=-4, end=4, value=b0, step=0.5, id='b_slider',
                      title="b_slider", callback=CustomJS.from_py_func(callback))
# callback.args["b_slider"] = b_slider_obj

layout = row(
    plot,
    widgetbox(a_slider_obj, b_slider_obj),
)

show(layout)

EDIT:

Seems that this actually does not work but that one should use the bokeh server for this. I leave the question open for now in case someone wants to post the solution using the server. I then either accept this answer, add an own one or delete the question again if no answer appears.

Cleb
  • 25,102
  • 20
  • 116
  • 151

1 Answers1

1

You're correct that you most likely need the server, especially if you want to use some Python functions that you can't easily express with JavaScript.

Here's the code that works. You can run it with bokeh serve. Note also that you pass some invalid numbers to np.power since e.g. -100 ** 2.1 is nan.

import numpy as np

from bokeh.layouts import row, widgetbox
from bokeh.models import Slider
from bokeh.plotting import figure, ColumnDataSource, curdoc


def foo(xval, a, b):
    print(xval.min(), xval.max(), np.isnan(xval).any(), a, b)
    return np.power(xval, a) + b


# some artificial data
a0 = 2.
b0 = 1.
x = np.linspace(-100., 100, 1000)
y = foo(x, a0, b0)

source = ColumnDataSource(data=dict(x=x, y=y))

plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

a_slider_obj = Slider(start=0, end=3, value=a0, step=0.1, id='a_slider', title="a_slider")
b_slider_obj = Slider(start=-4, end=4, value=b0, step=0.5, id='b_slider', title="b_slider")


def callback(attr, old, new):
    data = source.data
    # Since the callback is used by two sliders, we can't just use the `new` argument
    a_dynamic = a_slider_obj.value
    b_dynamic = b_slider_obj.value

    # Here I assume that you wanted to change the value and not just create an unused variable
    data['y'] = foo(data['x'], a_dynamic, b_dynamic)


a_slider_obj.on_change('value', callback)
b_slider_obj.on_change('value', callback)

layout = row(
    plot,
    widgetbox(a_slider_obj, b_slider_obj),
)

curdoc().add_root(layout)
Eugene Pakhomov
  • 9,309
  • 3
  • 27
  • 53
  • Nice that works fine. The range was indeed stupid, I first had another function and did not change it... What do the `attr, old, new` do in `callback`? They are not used directly but the code does not work when I remove them; what do they do under the hood? – Cleb Feb 13 '18 at 19:49
  • @Cleb `attr` is set to the attribute you pass as the first argument of `on_change`, `old` is the old attribute value, `new` is the new attribute value. Bokeh requires and checks for these arguments in callbacks to avoid potential user errors. Please mark the answer accepted if it helped you. – Eugene Pakhomov Feb 14 '18 at 15:24
  • 1
    Thanks for the additional info! Don't worry, I always accept if an answer solves my issues (and always upvote useful answers, too); feel free to check my question history. I just sometimes wait for a bit if something else shows up ;) – Cleb Feb 14 '18 at 15:29