Bokeh & Custom JS - Using a Slider to update a multiline graph

。_饼干妹妹 提交于 2021-01-28 08:11:15

问题


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!


回答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.




回答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)




回答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/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)


来源:https://stackoverflow.com/questions/54749589/bokeh-custom-js-using-a-slider-to-update-a-multiline-graph

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!