Slider not updating visualization

I am trying to change the visualized data by using a slider. Interestingly, on slider change, the console logs the expected values but the visualization is not updated as expected. I put the data into 2d arrays then use the slider value to index into the array. The working code is below.

Why is the code logging the right data but the graph is not being updated?

from bokeh.models import CustomJS, Slider
from bokeh.plotting import ColumnDataSource, figure, output_file, show
from bokeh.embed import file_html
from bokeh.resources import CDN
import pandas as pd

df_str = '''2020-09-10 1 75731 0
2020-09-10 2 71627 0
2020-09-10 3 66972 0
2020-09-10 4 63718 0
2020-09-10 5 61951 0
2020-09-11 1 59380 1
2020-09-11 2 58297 1
2020-09-11 3 57833 1
2020-09-11 4 55669 1
2020-09-11 5 55281 1'''

df = pd.DataFrame([col.split() for col in [row for row in df_str.split('\n')]])
df.columns = ['Date', 'Score', 'ScoreTotal', 'DayNumber']

dates = df.Date.unique()

xs = [[] for _ in range(len(dates))]
ys = [[] for _ in range(len(dates))]
date = [i for i in range(len(dates))]

for _, row in df.iterrows():
  i = int(row.DayNumber)
  xi = row.Score
  yi = row.ScoreTotal
  xs[i].append(xi)
  ys[i].append(yi)

x = xs[0]
y = ys[0]
xs += [[]] * 3
ys += [[]] * 3

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

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

date_slider = Slider(start=0, end=2, value=0, step=1, title="Date Slider")

callback = CustomJS(args=dict(source=source, date_slider=date_slider),
                    code="""
    const data = source.data;
    const date = date_slider.value
    const x = data['xs'][date]
    const y = data['ys'][date]
    console.log(y) // CORRECT CONSOLE LOG BUT NO CHANGE
    source.change.emit();
""")

date_slider.js_on_change('value', callback)

from bokeh.layouts import column, row

layout = row(
    plot,
    column(date_slider),
)

show(layout)

@Petar-Luketina your callback is not doing any work that would trigger any change. It is creates new local variables x and y inside the callback code, does nothing with them, and then they disappear then the callback finishes. To make any change happen you need to modify the actual data source:

source.data = {x: new_x_array, y: new_y_array}

Assigning a new value to source.data is what will trigger a plot update. There are lots of examples you can refer to in the docs.

Note if you re-assign source.data completely, Bokeh can automatically detect that. No call to source.change.emit() is needed. If you modify things ā€œin placeā€, e.g. source.data["x"] = ... that is when you have to help out by noting the change explicitly.

Hey @Bryan, thank you for your response! I donā€™t understand how to change the source.data if all that is being passed are parameters to the callback function. After looking at the documentation that you kindly linked, the example titled ā€œCustomJS for Model Property Eventsā€ appears to have similar JS code to my example. The code is also creating local variables x and y, with an added for loop.

How do we reassign source.data in the call back function?

The linked examples are different from your code. They grab a reference to an entire array that exists inside source.data, modify that array (in-place), then call source.change.emit()

    // this is reference to an existing array inside source.data
    const y = source.data['y'] 

    // this loops modifies the existing array by assigning to y[i]
    for (var i = 0; i < x.length; i++) {
        y[i] = Math.pow(x[i], f)
    }
    
    // this lets Bokeh know about the "in-place" change
    source.change.emit();

Contrast this with your code:

    // this just picks one number and gives it a name "y", nothing else
    const y = data['ys'][date]

The name ā€œyā€ is not automatically, magically a reference to anything Bokeh-related. That is not how javascript works. You are creating a new variable, unrelated to anything, and assigning it a single number, which has zero effect as far as Bokeh is concerned. It has the same effect for Bokeh as if you had written:

    const y = 10 // does not affect Bokeh state at all
2 Likes

@Bryan Thank you kindly! Your explanation helped me understand that modifying the old array in place works. The new code reads like:

  code="""
    const data = source.data;
    const date = date_slider.value;
    const old_y = data['y'];
    const x = data['xs'][date];
    const y = data['ys'][date];

    for (var i = 0; i < old_y.length; i++) {
      old_y[i] = y[i]
    }

    source.change.emit();
  """
1 Like