问题
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