问题
I have a pandas dataframe df where the first two columns represent x, y coordinates and the remaining columns represent time slices (t0,...tn) where the presence(1) or absence(0) of each point at each time slice (ti) is recorded.
I would like to use a RangeSlider
(not a Slider
) so that I can slide across a range of time slices and plot points that are present within that range.
This is what I got thus far,
from bokeh.layouts import column
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource
from bokeh.models.widgets import RangeSlider
# pts is a dataframe with columns (x, y, t0, t1,...t19)
src = ColumnDataSource(data = pts)
p = figure(plot_height = 500)
p.circle(source= src, x='x', y= 'y', size=2, color="navy", alpha=0.1)
callback = CustomJS( args = dict(source = src), code="""
var data = source.data;
// changed ti range
var ti_start = cb.obj.value[0] + 2 //offset
var ti_end = cb.obj.value[1] + 2
// change data (how to select columns???????)
data = data[ti_start:ti_end]
source.change.emit()
""")
ti_slider = RangeSlider(start=0, end=19, value=(1,2), step=1, title="Time Period",
callback = callback)
layout = column(ti_slider, p)
show(layout)
The above code does not work at all. The points are plotted and the RangeSlider appears but when I alter the range or slide across nothing happens. I am not able to restrict the columns that make up the data source (i.e. dataframe). I have tried changing the code that selects the columns but I don't know any javascript.
This is my first time trying to use the CustomJS
function with bokeh.
回答1:
There are a number of issues in the code above:
- It is
cb_obj
notcb.obj
- Use modern
js_on_change
, not very old ad-hoccallback
parameters - You are assigning to a local variable
data
and then throwing away the result— need to literally assign tosource.data
at some point for there to be any effect. - To do this by updating data sources you would need two data sources, on that always has the complete data that you pull from, and another that you only use to hold the subset. If you only have one data source and you subset it, you've now thrown away data you can never get back. (Future subsets will be against the current subset, not the whole)
- So, better to use
CDSView
for this, which lets you express an update-able subset view to apply to a constant data source. - JS does not have Pandas-like operations, you just have to do all the nested looping to check every row to determine the subset indices
- Just guessing, but you will probably want to fix x/y ranges if you intend to maintain the same spatial extent for comparison, as the slider moves.
Here is a simplified working example:
from bokeh.layouts import column
from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource, RangeSlider, CDSView, IndexFilter
source = ColumnDataSource(data=dict(
x=[1,2,3,4],
y=[1,1,1,1],
t0=[1,1,0,0],
t1=[0,1,0,0],
t2=[0,1,1,0],
t3=[0,0,1,1],
t4=[0,0,0,1],
t5=[0,0,0,0],
))
p = figure(plot_height=500, x_range=(0,5), y_range=(0,2))
view = CDSView(source=source, filters=[IndexFilter([0, 1])])
p.circle('x', 'y', size=10, color="navy", alpha=0.8,
source=source, view=view)
callback = CustomJS(args=dict(source=source, view=view), code="""
const start = cb_obj.value[0]
const end = cb_obj.value[1]
const indices = []
for (var i=0; i < source.get_length(); i++) {
for (var j=start; j<=end; j++) {
if (source.data["t" + j][i]==1) {
indices.push(i)
break
}
}
}
view.indices = indices
""")
ti_slider = RangeSlider(start=0, end=5, value=(0,1), step=1, title="Time Period")
ti_slider.js_on_change('value', callback)
show(column(ti_slider, p))
来源:https://stackoverflow.com/questions/58811783/interactively-change-a-point-plot-in-bokeh-using-rangeslider-to-select-columns-i