I have a general question about how data should be updated using CustomJS scripts in Bokeh, when a widget triggers a callback.
The following code works fine for me
import numpy as np
from bokeh.layouts import column, row
from bokeh.models import CustomJS, Slider
from bokeh.plotting import ColumnDataSource, figure, output_file, output_notebook, show
output_notebook()
# Define a slider
my_slider = Slider(start=0, end=2, value=0, step=1, title="Amplitude")
# Produce a list of data to be scrolled with the slider
xy_list = []
for a in range(0,3):
x_list = np.linspace(0, 10, 500)
y_list = float(a) * np.sin(x_list)
xy_list.append(
ColumnDataSource(data=dict(x=x_list, y=y_list))
)
# Produce the initial data to be displayed
# NOTE: this is like a hard-coded deepcopy,
# since deepcopy doesn't seem to work well
# with Bokeh objects
xy_current = ColumnDataSource(
data=dict(
x=np.linspace(0, 10, 500),
y=0.0*np.linspace(0, 10, 500)
)
)
# Produce a plot
plot = figure(y_range=(-10, 10), plot_width=200, plot_height=200)
plot.line('x', 'y', source=xy_current, line_width=3, line_alpha=0.6)
# Define a callback for the slider
callback = CustomJS(
args=dict(
# source_0=xy_source_0, # An instance of ColumnDataSource
source_curr=xy_current, # An instance of ColumnDataSource
source_list=xy_list, # A list, with entries of type ColumnDataSource
),
code="""
var data_curr = source_curr.data; // This is an instance of bokeh.core.property.wrappers.PropertyValueColumnData
var plot_i = cb_obj.value // This is an int
var old_x = data_curr['x'] // This is a numpy.ndarray
var old_y = data_curr['y'] // This is a numpy.ndarray
var new_x = source_list[plot_i].data['x'] // This is a numpy.ndarray
var new_y = source_list[plot_i].data['y'] // This is a numpy.ndarray
// Now update the y-values for each x, based on the slider value
for (var i = 0; i < old_x.length; i++) {
old_x[i] = new_x[i];
old_y[i] = new_y[i];
}
source_curr.change.emit();
""")
# Implement the callback
my_slider.js_on_change('value', callback)
# Show
layout = row(
plot,
my_slider,
)
show(layout)
However, it would be much more useful (for a bigger project unrelated to this code) if one could replace
for (var i = 0; i < old_x.length; i++) {
old_x[i] = new_x[i];
old_y[i] = new_y[i];
}
by something like this
old_x = new_x
old_y = new_y
I tried doing this, and the data doesn't get updated. Could someone explain why, and how to achieve this kind of higher lavel change of data (i.e. without having to change values of lists one-by-one)?
EDIT: after the answer by bigreddot, I updated the callback script to the following form, which uses fewer ocal variables.
callback = CustomJS(
args=dict(
source_curr=xy_current, # An instance of ColumnDataSource
source_list=xy_list, # A list, with entries of type ColumnDataSource
),
code="""
var plot_i = cb_obj.value // This is an int
// Now update the y-values for each x, based on the slider value
source_curr.data['x'] = source_list[plot_i].data['x']
source_curr.data['y'] = source_list[plot_i].data['y']
source_curr.change.emit();
""")
First of, to correct a misconception:
var old_x = data_curr['x'] // This is a numpy.ndarray
This is JavaScript executing in a browser where NumPy does not exist, so that it is either a JS Typed Array, or a plain JS array, but definitely not a numpy.ndarray
.
You have to actually update the values inside the source. When you do:
var old_x = data_curr['x']
You create a new local variable. If you then do:
old_x = new_x
Then all you have done is to assign a new value to the local variable. This does not affect the data source at all.
Instead, you need something like:
source_curr.data['x'] = new_x
That actually modifies the contents of the data source.