Bokeh dateSlider js code console.logs correct values from callback, but chart does not update

Bokeh version 2.4.3

I have a dataset consisting of 2-hourly output values for 2 types of machines, so 12 values for each machine per day. I plot them for a given day using a Bokeh vbar plot, as per the screenshot.

I have data for multiple days, so I was hoping to be able to select a day using dateSlider and have the plot update to show the data for whichever day I use the slider to select.

My code is below. My issue is, even though the callback seems to update the data the way I want, in that it console.logs what I expect when I drag the slider to a given date, there is no effect on the plot.

import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.transform import dodge, factor_cmap
from bokeh.models.callbacks import CustomJS
from bokeh.layouts import layout, column

from bokeh.models.widgets import DateSlider

from bokeh.palettes import GnBu3, OrRd3

d1 = {'type1': [0.8257115749525616, 0.8258538899430741, 0.8257115749525616, 0.8257115749525616, 0.8256641366223909, 0.8261385199240987, 0.8261859582542694, 0.8261385199240987, 0.8268026565464897, 0.8272770398481973, 0.8269924098671726, 0.8267552182163188, 0.8268026565464897, 0.827134724857685, 0.8268026565464895, 0.8268975332068311, 0.8268026565464895, 0.8266129032258065, 0.8273719165085389, 0.8274193548387097, 0.8268500948766604, 0.8265654648956356, 0.8263757115749526, 0.8267552182163188], 'type2': [0.09401622718052738, 0.10567951318458418, 0.17505070993914806, 0.29137931034482756, 0.23742393509127788, 0.09036511156186613, 0.09837728194726167, 0.10588235294117648, 0.07505070993914807, 0.12454361054766735, 0.2553752535496957, 0.45162271805273835, 0.5705882352941176, 0.7185598377281948, 0.731947261663286, 0.579107505070994, 0.5619675456389452, 0.5545638945233267, 0.5598377281947262, 0.36663286004056794, 0.33113590263691683, 0.23113590263691686, 0.16643002028397566, 0.15933062880324544], 'day': [1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight', 'A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}
d2 = {'type1': [0.3261287207863031, 0.27226244680837386, 0.055406015757283045, 0.3751453526115819, 0.8859150568977306, 0.42393128654219664, 0.014948390966778259, 0.8756807852660252, 0.18368866537360964, 0.30216318292301436, 0.6526416733411314, 0.8911577659955132], 'type2': [0.07247304906362291, 0.06050276595741642, 0.012312447946062899, 0.08336563391368486, 0.19687001264394013, 0.0942069525649326, 0.0033218646592840574, 0.19459573005911673, 0.040819703416357704, 0.06714737398289208, 0.1450314829646959, 0.19803505911011407], 'day': [1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}

source3 = ColumnDataSource(data=d2)

tod = d2['time_of_day']

p = figure(x_range=tod, y_range=(0, 1), title='type1 vs type2 by time of day',
           height=350, width=2000, toolbar_location=None, tools="", )

p.vbar(x=dodge('time_of_day', -0.1, range=p.x_range), top='type1', source=source3,
       width=0.2, color="#c9d9d3", legend_label='type1')

p.vbar(x=dodge('time_of_day',  0.1,  range=p.x_range), top='type2', source=source3,
       width=0.2, color="#718dbf", legend_label='type2')

p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

day_in_ms = 86400000
source = ColumnDataSource(d1)
source2 = ColumnDataSource(d1)

callback = CustomJS(args=dict(source=source, source2=source2),
                    code="""
    console.log(source.data);
    const data = source.data;
    const data2 = source2.data;
    const D = cb_obj.value; 
    const test_st = data2['day'].indexOf(D)

    const x = data2['day'];
    const data_day_len = data2['day'].length;
    //let unique = x.filter((item, i, ar) => ar.indexOf(item) === i);
   
    const ok = Object.keys(data)
    function update_data() {
        function bind_ts(){
            const st = x.indexOf(D);
            const en = st + 12;
            ok.forEach((key) => {
            console.log(D, st, en);
            data[key] = [];
            data[key] = data2[key].slice(st, en);
            });
        return data;
    }
    return bind_ts();
    }
    update_data();
    console.log(source.data);

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

start = d2['day'][0]
end = d2['day'][-1]
date_slider = DateSlider(start=d1['day'][0], end=d1['day'][-1], value=d1['day'][0], step=86400000, format='%A %B %d %Y', title='Pick a date')
date_slider.js_on_change('value', callback)
layout = layout(p, date_slider)
show(layout)

The last console.log shows that “source” is updating, in that it now indicates the data correctly corresponds to the cb_obj value, though I’m not sure that technically that’s right. In fact I’m pretty sure that’s wrong!

Any ideas why this isn’t working?

When I run the code and flip back and forth between the two slider values, it seems to print the same value for source.data every time. I’m guessing it’s a scoping issue an you are not actually updating source.data in place.

But it’s best not to update in place anyway. Offhand, I think this has just been overcomplicated. Best practice nowadays is to just build up a new dict from scratch and then assign:

source.data = new_data_dict

In that case you won’t need the call to source.change.emit either.

thanks for your fast reply. I thought the console.log output showed that the slider movement WAS updating the data – those are the values I expect (Sunday Feb 26 2023 when I drag all the way to the right, and Saturday Feb 25 when I drag it back left).

I agree, it’s a bit complicated – I had long forgotten what I once learned about JS scope. I will try your suggestion about assignment

I rewrote the callback js code, incorporating your suggestion on assignment – see below. Unfortunately, still no effect on the plot

callback = CustomJS(args=dict(source=source, source2=source2),
                    code="""
    const data = source.data;
    const data2 = source2.data;
    const D = cb_obj.value; 
    const st = data2['day'].indexOf(D);/* st and en are the indexes of each day's start and end: 12 2-hr periods */
    const en = st + 12;
    const newData = {}
    const ok = Object.keys(data2)
    ok.forEach((key) => {
        console.log(D, st, en);
        newData[key] =  data2[key].slice(st, en);
        });
    console.log(newData);
    source.data = newData;
    //source.change.emit();
""")

I ran with “source.change.emit()” commented out, but also no effect.

Perhaps I misunderstand whether the console.log is outputting what I think it should output. As far as I can see, it is changing the data as I intend.

Hi, Steve. I think the issue is due to the source’s name. The chart’ data source is source3, which is not a parameter in you JavaScript’s callback, so the callback of dateslider will not impact the plot, that the chart does not update.
The code below has a warning due to the “inconsistent lengths”, please let me know if there a better way or code to solve it. Thanks.

d1 = {'type1': [0.8257115749525616, 0.8258538899430741, 0.8257115749525616, 0.8257115749525616, 0.8256641366223909, 0.8261385199240987, 0.8261859582542694, 0.8261385199240987, 0.8268026565464897, 0.8272770398481973, 0.8269924098671726, 0.8267552182163188, 0.8268026565464897, 0.827134724857685, 0.8268026565464895, 0.8268975332068311, 0.8268026565464895, 0.8266129032258065, 0.8273719165085389, 0.8274193548387097, 0.8268500948766604, 0.8265654648956356, 0.8263757115749526, 0.8267552182163188], 'type2': [0.09401622718052738, 0.10567951318458418, 0.17505070993914806, 0.29137931034482756, 0.23742393509127788, 0.09036511156186613, 0.09837728194726167, 0.10588235294117648, 0.07505070993914807, 0.12454361054766735, 0.2553752535496957, 0.45162271805273835, 0.5705882352941176, 0.7185598377281948, 0.731947261663286, 0.579107505070994, 0.5619675456389452, 0.5545638945233267, 0.5598377281947262, 0.36663286004056794, 0.33113590263691683, 0.23113590263691686, 0.16643002028397566, 0.15933062880324544], 'day': [1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight', 'A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}
d2 = {'type1': [0.3261287207863031, 0.27226244680837386, 0.055406015757283045, 0.3751453526115819, 0.8859150568977306, 0.42393128654219664, 0.014948390966778259, 0.8756807852660252, 0.18368866537360964, 0.30216318292301436, 0.6526416733411314, 0.8911577659955132], 'type2': [0.07247304906362291, 0.06050276595741642, 0.012312447946062899, 0.08336563391368486, 0.19687001264394013, 0.0942069525649326, 0.0033218646592840574, 0.19459573005911673, 0.040819703416357704, 0.06714737398289208, 0.1450314829646959, 0.19803505911011407], 'day': [1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}

sourcePlot = ColumnDataSource(data=d2)

tod = d2['time_of_day']

p = figure(x_range=tod, y_range=(0, 1), title='type1 vs type2 by time of day',
           height=350, width=2000, toolbar_location=None, tools="", )

p.vbar(x=dodge('time_of_day', -0.1, range=p.x_range), top='type1', source=sourcePlot,
       width=0.2, color="#c9d9d3", legend_label='type1')

p.vbar(x=dodge('time_of_day',  0.1,  range=p.x_range), top='type2', source=sourcePlot,
       width=0.2, color="#718dbf", legend_label='type2')

p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

day_in_ms = 86400000
source1 = ColumnDataSource(d1)
source2 = ColumnDataSource(d2)

callback = CustomJS(args=dict(sourcePlot=sourcePlot, source1=source1, source2=source2),
                    code="""
    const data = sourcePlot.data;
    const data1 = source1.data;
    const data2 = source2.data;
    const D = cb_obj.value; 
    console.log(data1['day'][0])
    console.log(D)
    console.log(data1['day'][0])
    if (D == data1['day'][0]) {
        data['time_of_day'] = data1['time_of_day'];
        data['type1'] = data1['type1'];
        data['type2'] = data1['type2'];
    } else {
        data['time_of_day'] = data2['time_of_day'];
        data['type1'] = data2['type1'];
        data['type2'] = data2['type2'];
    }
    sourcePlot.change.emit();    
""")

start = d2['day'][0]
end = d2['day'][-1]
date_slider = DateSlider(start=d1['day'][0], end=d1['day'][-1], value=d1['day'][0], step=86400000, format='%A %B %d %Y', title='Pick a date')
date_slider.js_on_change('value', callback)
layout = layout(p, date_slider)
show(layout)

thanks RajonDawn, this may point to a solution – at least the plot does update from the callback. I’ll study it and report back.

ok, cracked it.

RajonDawn, you were right about the names – good catch. That’s what got me to the solution.

On the js callback, I think the issue there was my rebuilding of the entire dict, including ‘day’, which was unnecessary. So your suggestion there was also very helpful.

Bryan, you’ll notice no call to sourcePlot.change.emit()!

Here’s the full version of what worked, without any Python or JS warnings:

import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.transform import dodge, factor_cmap
from bokeh.models.callbacks import CustomJS
from bokeh.layouts import layout, column

from bokeh.models.widgets import DateSlider

from bokeh.palettes import GnBu3, OrRd3
d1 = {'type1': [0.8257115749525616, 0.8258538899430741, 0.8257115749525616, 0.8257115749525616, 0.8256641366223909, 0.8261385199240987, 0.8261859582542694, 0.8261385199240987, 0.8268026565464897, 0.8272770398481973, 0.8269924098671726, 0.8267552182163188, 0.8268026565464897, 0.827134724857685, 0.8268026565464895, 0.8268975332068311, 0.8268026565464895, 0.8266129032258065, 0.8273719165085389, 0.8274193548387097, 0.8268500948766604, 0.8265654648956356, 0.8263757115749526, 0.8267552182163188], 'type2': [0.09401622718052738, 0.10567951318458418, 0.17505070993914806, 0.29137931034482756, 0.23742393509127788, 0.09036511156186613, 0.09837728194726167, 0.10588235294117648, 0.07505070993914807, 0.12454361054766735, 0.2553752535496957, 0.45162271805273835, 0.5705882352941176, 0.7185598377281948, 0.731947261663286, 0.579107505070994, 0.5619675456389452, 0.5545638945233267, 0.5598377281947262, 0.36663286004056794, 0.33113590263691683, 0.23113590263691686, 0.16643002028397566, 0.15933062880324544], 'day': [1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677283200000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0, 1677369600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight', 'A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}
d2 = {'type1': [0.3261287207863031, 0.27226244680837386, 0.055406015757283045, 0.3751453526115819, 0.8859150568977306, 0.42393128654219664, 0.014948390966778259, 0.8756807852660252, 0.18368866537360964, 0.30216318292301436, 0.6526416733411314, 0.8911577659955132], 'type2': [0.07247304906362291, 0.06050276595741642, 0.012312447946062899, 0.08336563391368486, 0.19687001264394013, 0.0942069525649326, 0.0033218646592840574, 0.19459573005911673, 0.040819703416357704, 0.06714737398289208, 0.1450314829646959, 0.19803505911011407], 'day': [1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0, 1669593600000.0], 'time_of_day': ['A\nMidnight to 2am', 'B\n2am to 4am', 'C\n4am to 6am', 'D\n6am to 8am', 'E\n8am to 10am', 'F\n10am to noon', 'G\nNoon to 2pm', 'H\n2pm to 4pm', 'I\n4pm to 6pm', 'J\n6pm to 8pm', 'L\n8pm to 10pm', 'M\n10pm to mignight']}

sourcePlot = ColumnDataSource(data=d2)

tod = d2['time_of_day']

p = figure(x_range=tod, y_range=(0, 1), title='type1 vs type2 by time of day',
           height=350, width=2000, toolbar_location=None, tools="", )

p.vbar(x=dodge('time_of_day', -0.1, range=p.x_range), top='type1', source=sourcePlot,
       width=0.2, color="#c9d9d3", legend_label='type1')

p.vbar(x=dodge('time_of_day',  0.1,  range=p.x_range), top='type2', source=sourcePlot,
       width=0.2, color="#718dbf", legend_label='type2')

p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

day_in_ms = 86400000
source1 = ColumnDataSource(d1)
source2 = ColumnDataSource(d1)

callback = CustomJS(args=dict(sourcePlot=sourcePlot, source2=source2),
                    code="""
    const data = sourcePlot.data;
    const data2 = source2.data;
    const D = cb_obj.value; 
    const st = data2['day'].indexOf(D);/* st and en are the indexes of each day's start and end: 12 2-hr periods */
    const en = st + 12;
    const newData = {}
    const ok = ['time_of_day', 'type1', 'type2'];
    ok.forEach((key) => {
        console.log(D, st, en);
        newData[key] =  data2[key].slice(st, en);
        });
    console.log(newData);
    sourcePlot.data = newData;
    //sourcePlot.change.emit();
                    """)

start = d2['day'][0]
end = d2['day'][-1]
date_slider = DateSlider(start=d1['day'][0], end=d1['day'][-1], value=d1['day'][0], step=86400000, format='%A %B %d %Y', title='Pick a date')
date_slider.js_on_change('value', callback)
layout = layout(p, date_slider)
show(layout)

My profound thanks to everyone who helped – great community!

1 Like

Hi, Steve. Thanks for your reply. I run the latest code you post, and found that “type1” bar will not update since the first move of dateslider, but “type2” bar will work correctly. Maybe the code need a optimization?