Search code examples
javascriptpythonbokeh

updating plot data with bokeh slider widget


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();
""")

Solution

  • 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.