2
votes

I am trying to use a slider to update my Bokeh Plot. I am finding it difficult to achieve it using pandas dataframe(did not find any examples so far). The other way is to use the "columndatasource" (found some examples over forums) but still not able to achieve the functionality. So I have two columns, X axis is date and the Y axis is Volume. I want to change my Y values based on slider input. I am able to see the plot but the slider functionality is not working

Any help will be very much appreciable.

source = ColumnDataSource(data=dict(x=df2['Date'],y=df2['Vol']))
S1 = figure(plot_width=400,plot_height=400,tools=TOOLS1,title="Volume Per Day",x_axis_type="datetime")
S1.line('x','y',source=source)

callback_test = CustomJS(args=dict(source=source), code="""
var data = source.get('data');
var s_val = cb_obj.value
x = data['x']
y = data['y']
console.log(cb_obj) 
for (i = 0; i < s_val; i++) {
    y[i] = y[i]            
    }
source.trigger('change');
""")

slider = Slider(start=0, end= max_Vol, value=1, step=100,title="Vol Per Day",callback=callback_test)
3
With i from 0 to s_val, you are just replacing each y[i] by itself, so nothing changes.Seb
@Seb , any idea how to achieve it..MoChen
In your post you say "I want to change my Y values based on slider input.", how exactly do you want your Y values to change?Seb
based on the slider value the dataframe's "Vol" column is selected. e.g. if y= 4000 then df2[vol] < 4000 are selected and plotted against the time.MoChen

3 Answers

1
votes

You are trying to update the range of data that is plotted using a slider.

When you do:

y = data['y']
for (i = 0; i < s_val; i++) {
    y[i] = y[i]            
    }

the python equivalent would be, if y is some array with length>s_val:

for i in range(s_val):
    y[i] = y[i]

This just replaces the elements from 0 to s_val-1 by themselves and doesn't change the rest of the list.

You can do two things:

  • update the displayed axis range directly
  • use an empty source that you will fill from your existing source based on the slider value

.

source = ColumnDataSource(data=dict(x=df2['Date'],y=df2['Vol']))
fill_source = ColumnDataSource(data=dict(x=[],y=[]))
S1 = figure(plot_width=400,plot_height=400,tools=TOOLS1,title="Volume Per Day",x_axis_type="datetime")
S1.line('x','y',source=fill_source)

callback_test = CustomJS(args=dict(source=source,fill_source=fill_source), code="""
var data = source.data;
var fill_data = fill_source.data;
var s_val = cb_obj.value;
fill_data['x']=[];
fill_data['y']=[];
for (i = 0; i < s_val; i++) {
    fill_data['y'][i].push(data['y'][i]);
    fill_data['x'][i].push(data['x'][i]);          
    }
fill_source.trigger('change');
""")
1
votes

Here is the changes I have made to make it work with Bokeh last version

Some syntax error in the JavaScript part have been corrected, the method to trigger change is now change.emit, and the callback for a stand alone document is set after the Slider definition thanks to js_on_change

I have added all the import commands to give a complete example I have also changed the visualization to show only data below the number of flight set by the slider (for more comprehension when moving the Slider towards lower values)

Below is the resulting code:

from bokeh.layouts import column, widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Slider
from bokeh.plotting import Figure
import pandas as pd
from datetime import datetime, date, timedelta
from bokeh.plotting import show
from random import randint

today = date.today()
random_data = [[today + timedelta(days = i), randint(0, 10000)] for i in range(10)]
df2 = pd.DataFrame(random_data, columns = ['Date', 'Vol'])

source = ColumnDataSource(data = dict(x = df2['Date'], y = df2['Vol']))
fill_source = ColumnDataSource(data = dict(x = df2['Date'], y = df2['Vol'])) # set the graph to show all data at loading
TOOLS1 = []
S1 = Figure(plot_width = 400, plot_height = 400, tools = TOOLS1, title = "Volume Per Day", x_axis_type = "datetime")
S1.line('x', 'y', source = fill_source)

callback_test = CustomJS(args = dict(source = source, fill_source = fill_source), code = """
var data = source.data;
var fill_data = fill_source.data;
var s_val = cb_obj.value;
fill_data['x']=[];
fill_data['y']=[];
for (var i = 0; i <= data.x.length; i++) {   // added "var" declaration of variable "i"
        if (data['y'][i] <= s_val) { // more meaningful visualization: assuming you want to focuss on dates with less number of flights
            fill_data['y'].push(data['y'][i]); // [i] index on left side of assignment removed
        }
        else {
            fill_data['y'].push(0);
        }
        fill_data['x'].push(data['x'][i]);
    }
    fill_source.change.emit() ; // "trigger" method replaced by "change.emit"
""")

max_Vol = df2['Vol'].max()
slider = Slider(start = 0, end = max_Vol, value = max_Vol, step = 100, title = "Vol Per Day") # Remove attribute "callback = callback_test"
slider.js_on_change('value', callback_test) # new way of defining event listener

controls = widgetbox(slider)
layout = column(controls, S1)
show(layout)

Would be nice if I could embbed the resulting (HTML) visualization directly in this answer, let me now if it's possible ;)

-1
votes

Here a working example hope u can make it :)

from os.path import dirname, join
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DateRangeSlider,DatePicker,DateFormatter, DataTable, TableColumn, NumberFormatter
from bokeh.io import curdoc
from datetime import datetime,date
import datetime
df = pd.read_csv(join(dirname(__file__), 'test.csv'))
df['dat'] = pd.to_datetime(df['date'])
source = ColumnDataSource(data=dict())

def update():
current = df[(df['dat'] >=  pd.to_datetime(slider.value[0])) & (df['dat'] <=  pd.to_datetime(slider.value[1]))]
  source.data = {
    'opens'             : current.open,
    'dates'           : current.date,
  }

slider = DateRangeSlider(title="Date Range: ", start=date(2010, 1, 1), end=date.today(), value=(date(2017, 9, 7),date.today()), step=1)
slider.on_change('value', lambda attr, old, new: update())




columns = [
   TableColumn(field="dates", title="Date" ,formatter=DateFormatter()),
    TableColumn(field="opens", title="open"),


data_table = DataTable(source=source, columns=columns, width=800)

controls = widgetbox(slider)
table = widgetbox(data_table)

curdoc().add_root(row(controls, table))

update()

here is data in the the test.csv file

date,open
951868800000,102
951955200000,100.5
952041600000,107.25
952300800000,109.94
952387200000,106
952473600000,103
952560000000,106.5
952646400000,107.62
952905600000,104
952992000000,107.75
953078400000,107.5
953164800000,109
953251200000,108.25
953510400000,110
953596800000,112.81
953683200000,114.5
953769600000,115.87
953856000000,115.37
954115200000,125
954201600000,125.75
954288000000,122.31
954374400000,118.87
954460800000,122.62
954720000000,120
954806400000,121.5
954892800000,120.5
954979200000,123.5
955065600000,123.5
955324800000,124.75
955411200000,121.62
955497600000,119.62
955584000000,112.5
955670400000,109.81
955929600000,103.87
956016000000,112.25

run code using bokeh serve filename.py