Skip to content Skip to sidebar Skip to footer

Bokeh & Custom Js - Using A Slider To Update A Multiline Graph

I am using Bokeh to produce an interactive time-series graph. There can be n number of series displayed simultaneously. Each series will display from t= 0 to t = x, with x being t

Solution 1:

The other answers are both partially correct, but incomplete or have issues in various ways. The major missing part is that if you slice the original data source every time the slider moves, then the after the first slider move, you are now no longer slicing the original data anymore, so things will not work. You need to send the full original data separately, and always copy the sub-parts you want out of the original. Here is a complete working script:

from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import figure

data_dict = {
    'lons':[[-1.0, -1.1, -1.2, -1.3, -1.4], [-1.0, -1.1, -1.25, -1.35, -1.45]],
    'lats':[[53.0, 53.1, 53.2, 53.3, 53.4], [53.05, 53.15, 53.25, 53.35, 53.45]]
}

full_source = ColumnDataSource(data_dict)
source = ColumnDataSource(data_dict)

p = figure(plot_width=400, plot_height=400, tools="")
p.multi_line(xs='lons', ys='lats', source=source)

callback = CustomJS(args = dict(source=source, full_source=full_source), code = """
    const time = cb_obj.value;
    const full_lons = full_source.data['lons']
    const full_lats = full_source.data['lats']

    for(i=0; i<full_lons.length; i++) {
        source.data['lons'][i] = full_lons[i].slice(0, time)
        source.data['lats'][i] = full_lats[i].slice(0, time)
    }
    // only need this because source.data is being updated "in place"
    source.change.emit()
    """)

slider = Slider(start = 0, end = 5, value = 0, step = 1, callback = callback)
slider.js_on_change('value', callback)
layout = column(p, slider)

show(layout)

I've updated the code to use figure from bokeh.plotting to be simpler, and also to get default axes, etc. It's also worth noting that a slider value of 0 may not make sense, a plot with that will be (correctly) empty.

Solution 2:

I believe what you are looking for is covered in the documentation - please see : https://hub.mybinder.org/user/bokeh-bokeh-notebooks-ykp39727/notebooks/tutorial/06%20-%20Linking%20and%20Interactions.ipynb#Slider-widget-example

Specifically, something like the following slider.js_on_change('value', calback)

Solution 3:

There were some small problems in your javascript code (You forgot a ) and you forgot to assign the lists with new lons/lats to the source.), a syntax error in the Slider and a typo in the imports.

#!/usr/bin/python3from bokeh.models import CustomJS, ColumnDataSource, Slider, Plot
from bokeh.models.glyphs import MultiLine
from bokeh.io import show
from bokeh.layouts import column

data_dict = {'lons':[[-1.0, -1.1, -1.2, -1.3, -1.4], [-1.0, -1.1, -1.25, -1.35, -1.45]], 'lats':[[53.0, 53.1, 53.2, 53.3, 53.4], [53.05, 53.15, 53.25, 53.35, 53.45]]}

source = ColumnDataSource(data_dict)
p = Plot(title = None, plot_width = 400, plot_height = 400)
glyph = MultiLine(xs = 'lons', ys = 'lats')
p.add_glyph(source, glyph)

callback = CustomJS(args = dict(source = source), code = """
    var data = source.data;
    var time = time.value;
    var lons = data['lons']
    var lats = data['lats']
    var runners = lons.length
    var new_lons = []
    var new_lats = []

    for(i=0; i<runners; i++){
        var runner_lons = lons[i].slice(0, time)
        var runner_lats = lats[i].slice(0, time)
        new_lons.push(runner_lons)
        new_lats.push(runner_lats)
    }
    lons = new_lons
    lats = new_lats
    source.attributes.data.lons = new_lons
    source.attributes.data.lons = new_lats
    source.change.emit();
    """)

slider = Slider(start = 0, end = 5, value = 0, step = 1, callback = callback)
layout = column(p, slider)
callback.args["time"] = slider

show(layout)

Post a Comment for "Bokeh & Custom Js - Using A Slider To Update A Multiline Graph"