2

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 the value created by a slider.

I'm using ColumnDataSource to contain it all, MultiLine glyph to display the series, Slider for the slider and CustomJS to control the update interaction.

from bokeh.models import CustomJS, ColumnDataSource, Slider, Plot
from bokeh.models.glyph 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.change.emit();
    """)

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

show(layout)

This code renders the graph, with both lines drawn covering all points in source.data.

Moving the slider will update the data in lons & lats as intended, but the graph display does not update.

Pointers, recommendations, suggestions, explanations all very gratefully received!

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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)

Comments

0

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/python3
from 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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.