I am having a Pandas DataFrame with a TimeSeries Index. The idea is to use the bokeh server in order to have a DateSlider. This means the values that I pass over the function are dates. However, I think my problem is the data is not updated when I am running the bokeh server. The Slider appears but the data is not updating while moving the cursor from the Slider. I would like to have your view. This is my code:
#The file was already imported and cleaned as DF: trt_file
day_one = str(min(trt_file.index)) #Checking point file
day_last = str(max(trt_file.index)) #Checking point file
# Creating the data source which will be selected from the whole file
source = ColumnDataSource(data={
"x" : trt_file.loc[day_one]["Depth [m]"],
"y" : trt_file.loc[day_one]["Temperature"],
"Temperature" : trt_file.loc[day_one]["Temperature"],
"Depth" : trt_file.loc[day_one]["Depth [m]"]
})
#Minimum values for axis generation
xmin, xmax = min(trt_file["Depth [m]"]), max(trt_file["Depth [m]"])
ymin, ymax = min(trt_file["Temperature"]), max(trt_file["Temperature"])
#Set the figure fopr the initial draw
p = figure(title="2018-07-09", x_axis_label="Depth [m]", y_axis_label="Temperature", plot_height=300, plot_width=1500, x_range=(xmin, xmax), y_range=(ymin, ymax),tools=[HoverTool(tooltips=[("Temperature", "#Temperature"), ("Depth [m]", "#Depth")])] )
#Draw the values from the source we defined first
p.circle(x="x", y="y", fill_alpha=0.5, source=source)
#Set the axis names
p.xaxis.axis_label = "Kable lenght [m]"
p.yaxis.axis_label = "Temperature [C]"
#Creating the DateTime for the Slider
day_one_dt = (min(trt_file.index)) #Checking point file
day_last_dt = (max(trt_file.index))
#Slider
slider = DateSlider(start=day_one, end=day_last, value=day_one, step=1, title="TRT Day")
#Creating the bokeh interactive figure
def update_plot(attr, old, new): #attribute to change, old_val, new_val
#set day for the slider value
day_start = (str(slider.value)) #Value from the slider
new_data = {
"x" : trt_file.iloc[day_start]["Depth [m]"],
"y" : trt_file.iloc[day_start]["Temperature"],
"Temperature": trt_file.iloc[day_start]["Temperature"],
"Depth" : trt_file.iloc[day_start]["Depth [m]"]
}
source.trt_file = new_data
p.title.text = "Day TRT"
slider.on_change("value", update_plot)
layout = row(widgetbox(slider), p)
curdoc().title = "Temp"
curdoc().add_root(layout)
Bokeh knows nothing at all about a "trt file" so setting source.trt_file is not something Bokeh will notice or do anything with. The property Bokeh knows about and responds to is data:
source.data = new_data
Related
Need help with below code, my hover over is not showing any data just ???. Im guessing its because I havent defined the source properly or I need to include an argument in the vbar code. Do I need to add more info to the source e.g. column names etc or do I also need to refer to the source and column names in the vbar arguments?
Thanks
def get_width():
mindate = df['local_time'].min()
maxdate = df['local_time'].max()
return 0.8 * (maxdate-mindate).total_seconds()*1000 / len(df['local_time'])
plots = []
sliders = []
for t in df['timeframeID'].unique():
inc = df[df['timeframeID'] == t].close > df[df['timeframeID'] == t].open
dec = df[df['timeframeID'] == t].open > df[df['timeframeID'] == t].close
source = ColumnDataSource(data=df)
TOOLS = "pan,wheel_zoom,box_zoom,crosshair,reset,save"
TOOLTIPS = [('open', '#open'),('high', '#high'),('low', '#low'),('close', '#close')]
name1= figure(plot_width=1600, plot_height = 900, title="Instrument AUDUSD: "+t, tools = TOOLS, tooltips=TOOLTIPS)
name1.xaxis.major_label_overrides = {
i: date.strftime('%b %d') for i, date in enumerate(pd.to_datetime(df["local_time"]))
}
name1.xaxis.bounds = (0, df.index[-1])
name1.segment(df[df['timeframeID'] == t].index[inc], df[df['timeframeID'] == t].high[inc],
df[df['timeframeID'] == t].index[inc],df[df['timeframeID'] == t].low[inc], color="black")
name1.segment(df[df['timeframeID'] == t].index[dec], df[df['timeframeID'] == t].high[dec],
df[df['timeframeID'] == t].index[dec],df[df['timeframeID'] == t].low[dec], color="black")
#name1.y_range.range_padding = 0.05
name1.vbar(df[df['timeframeID']== t].index[inc], 0.5, df[df['timeframeID']== t].open[inc], df[df['timeframeID']== t].close[inc],
fill_color="green", line_color="green")#, width=get_width())
name1.vbar(df[df['timeframeID']== t].index[dec], 0.5, df[df['timeframeID']== t].open[dec], df[df['timeframeID']== t].close[dec],
fill_color="#F2583E", line_color="#F2583E")#, width=get_width())
r = name1.circle(df[df['timeframeID']== t].index, df[df['timeframeID']== t].AV, alpha = 1, radius = .20)
name1.y_range.range_padding = 0.05
callback = CustomJS(args=dict(renderer=r), code="""
renderer.glyph.radius = cb_obj.value;
""")
s = Slider(start=0, end=1.5, value=.20, step=.05, title="Radius - " + t)
s.js_on_change('value', callback)
output_notebook()
output_file("candlestick.html", title="candlestick.py example")
sliders.append(s)
plots.append(name1)
show(column(
row(
*plots),*sliders))
Here is what my dataframe, df, looks like:
Currently you are directly providing data only for the x and y coordinate. Bokeh does not know anything about the other data. To make bokeh aware of all the data you must pass a source via source=source in your vbar method. When you pass the source, bokeh gets all the data so that it can look at different columns to show when hovering.
When you pass a source, you cannot pass the x, top and bottom coordinates directly, because otherwise bokeh would not know how to associate these values to the source you passed¹. So when you pass a source, you want to pass the names of the x, top and bottom coordinate columns, instead of the data directly. So you want to write something like:
name1.vbar("index", "open", "close", source=source, fill_color="green", line_color="green")
To do this you need to construct a source/DataFrame which already has the data you want, instead of doing the filtering you are doing in the vbar call. Without seeing your data I cannot tell you how you would construct such a Dataframe though.
1: Actually bokeh associates directly passed data via the index, so the first value is associated with the first line in the source.
I am using Bokeh 1.0.1. I am unable to update the data source in the Update method i.e src.data.update(new_src.data) doesn't seem to work. Below is the full code.
def modify_doc(doc):
def create_dataset(df, resample='D'):
# Resample the data
src = df.resample(resample).mean()
# Reset index for hovering
src.reset_index(inplace=True)
return ColumnDataSource(src)
def create_plot(src):
# Blank plot with correct labels
p = figure(plot_width=700, plot_height=300, x_axis_type="datetime",
title = 'Variation of Pollution',
x_axis_label = 'Time', y_axis_label = 'Pollution (µg/m³)')
p.line(source=src, x='Date & Time', y='pm2.5', line_width=2,
color='firebrick', line_alpha=0.5, legend='Actual')
hover = HoverTool(tooltips=[('Pollution', '#{pm2.5} µg/m³'),
('Air Temp', '#{Air Temp.} °C'),
('Temp', '(#{Min. Temp.}{0.2f}, #{Max. Temp.}{0.2f}) °C'),
('Dew Pt.', '#{Dew Pt.} °C'),
('Rainfall', '#Rainfall mm'),
('Wind Dir.', '#{Wind Dir.} °'),
('Wind Speed', '#{Wind Speed} km/hr'),
('Relative humidity', '(#{Min. RH}{0.2f}, #{Max. RH}{0.2f}) %')],
mode='vline')
p.add_tools(hover)
p.legend.click_policy = 'hide'
return p
# Update function takes three default parameters
def update(attr, old, new):
# Resampling list
re_list = ['D', 'W', 'M', 'A']
# Make a new dataset based on the selected carriers and the
# make_dataset function defined earlier
new_src = create_dataset(df,
resample = re_list[resample_button_group.active])
# Update the source used the quad glpyhs
src.data.update(new_src.data)
resample_button_group = RadioButtonGroup(labels=["Day", "Week", "Month", "Year"], active=1)
resample_button_group.on_change('active', update)
controls = WidgetBox(resample_button_group)
# Initial Plot
src = create_dataset(df)
p = create_plot(src.data)
layout = row(controls, p)
doc.add_root(layout)
# Set up an application
handler = FunctionHandler(modify_doc)
app = Application(handler)
You should be able to update the line glyph directly.
First, modify your plotting code to assign a name to the line glyph:
pm_line = p.line(
source=src,
x='Date & Time',
y='pm2.5',
line_width=2,
color='firebrick',
line_alpha=0.5,
legend='Actual',
name='pm_line' # Add this!
)
Then in your update function, replace your existing update line with the following:
pm_line = p.select_one({'name':'pm_line'})
pm_line.data_source.data = new_src.data
I'm trying to create a bokeh plot of the US States, and color each of the state according to some data. Now using this tutorial I managed to create this, but I also want to enhance it, and add a slider to it, to change the values displayed. For example like displaying separate years.
With the help of this tutorial, I managed to add the slider, and the underlying data does change, according to the hover text, but the colors aren't recalculated, and so the visual representation does not match the values.
This is the code I've used, from a Jupyter notebook, so anybody who wants to try can reproduce
from bokeh.io import show, output_notebook
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper,
Range1d, CustomJS, Slider
)
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.layouts import row, widgetbox
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.us_states import data as states
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import random
output_notebook()
palette.reverse()
states_accumulated ={}
available_state_codes = states.keys()
for key, value in counties.items():
state_name = value["state"].upper()
if state_name in states.keys() and "number" not in states[state_name]:
states[state_name]["number"] = key[0]
for key,state in states.items():
state["code"] = key
state_list = []
for key,state in states.items():
state_list.append(state)
unemployment_transf = []
for key,value in unemployment.items():
unemployment_transf.append({
"State":key[0],
"County":key[1],
"Value":value
})
unemp_df = pd.DataFrame(unemployment_transf)
unemp_sum = unemp_df.groupby("State").mean()["Value"]
unemp_sum = unemp_sum.sort_index()
unemp_sum_flat = {key:value for key, value in unemp_sum.items()}
for state in state_list:
state["value"] = unemp_sum_flat[state["number"]]
state_df = pd.DataFrame(state_list)
color_mapper = LogColorMapper(palette=palette)
state_xy = (list(state_df["lons"].values),list(state_df["lats"].values))
max_x = max([max(l) for l in state_xy[0]])
max_y = max([max(l) for l in state_xy[1]])
min_x = min([min(l) for l in state_xy[0]])
min_y = min([min(l) for l in state_xy[1]])
data=dict(
x=state_xy[0],
y=state_xy[1],
name=list(state_df["name"].values),
used = list(state_df["value"].values)
)
data['1999'] = list(state_df["value"].values)
data['2000'] = [random.randrange(0,10) for i in range(len(state_xy[0]))]
source = ColumnDataSource(data)
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="States", tools=TOOLS,
x_axis_location=None, y_axis_location=None
)
p.width=450
p.height = 450
p.x_range= Range1d(-170,-60)
p.y_range = Range1d(min_y-10,max_y+10)
p.grid.grid_line_color = None
renderer = p.patches('x', 'y', source=source,
fill_color={'field': 'used', 'transform': color_mapper},
fill_alpha=0.7, line_color="white", line_width=0.5)
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("Name", "#name"),
("Unemployment rate)", "#used%"),
("(Long, Lat)", "($x, $y)"),
]
callback = CustomJS(args=dict(source=source,plot=p,color_mapper = color_mapper,renderer = renderer), code="""
var data = source.data;
var year = year.value;
used = data['used']
should_be = data[String(year)]
for (i = 0; i < should_be.length; i++) {
used[i] = should_be[i];
}
""")
year_slider = Slider(start=1999, end=2000, value=1999, step=1,
title="year", callback=callback)
callback.args["year"] = year_slider
layout = row(
p,
widgetbox(year_slider),
)
show(layout)
Sample images of the plot:
What I would like to accomplish, is that when I change the slider, the colors on the plot should change. Now I think the JS callback should call some kind of redraw or recalculate, but I haven't found any documentation about it. Is there a way to do this?
append source.change.emit() to the Javascipt code to trigger the change event.
Appending source.trigger("change"); to the CustomJS seems to solve the problem, now as the slider changes, the colors change.
I'm trying to create slider that as you drag the slider, the portion of the graph that is shown is only what is on the slider. For example, if you look at my graph below, if the slider was set to 1990, you would only see the lines from 1990 to 2016. I found a working example with plotly but I wanted to see if it could be done with Bokeh.
This is my code so far:
p = figure(width = 900, height = 450)
p.xaxis.axis_label = 'Year'
p.yaxis.axis_label = 'Aggregated Number of Degrees in Education'
source = ColumnDataSource(df)
fill_source = ColumnDataSource(data=dict(x=[],y=[]))
# Create objects for each line that will be plotted
stem = p.line('year', 'stem', line_color='#8dd3c7', line_width=3, source=source)
stem = p.circle('year', 'stem', line_color='#8dd3c7', line_width=3, source=source)
sped = p.line('year', 'sped', line_color='#fdb462', line_width=3, source=source)
elem = p.line('year', 'elem', line_color='#bebada', line_width=3, source=source)
elem = p.square('year', 'elem', line_color='#bebada', line_width=3, source=source)
other = p.line('year', 'other', line_color='#fb8072', line_width=4, source=source)
aggtotal = p.line('year', 'aggtotal', line_dash=[4,4,], line_color='#80b1d3', line_width=3, source=source)
yaxis = p.select(dict(type=Axis, layout="left"))[0]
yaxis.formatter.use_scientific = False
legend = Legend(items=[("STEM", [stem])
,("SPED" , [sped])
,("Elementary", [elem])
,("Other", [other])
,("Total Education Graduates", [aggtotal])], location=(0, 0))
p.add_tools(HoverTool(tooltips=[("Date", "#year")]))
p.add_layout(legend, 'right')
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');
""")
sped_slider = Slider(start=1984, end= 2016, value=1, step=1,title="Year",callback=callback_test)
callback_test.args["sped"] = sped_slider
layout = row(p,widgetbox(sped_slider))
This renders a slider but it doesn't do anything and I'm not sure where to go from here.
There are some issues with your callback code. For example:
you loop i from 0 to s_val (which may be 1990) which is not consistent with the length of your arrays.
your glyphs references columns 'stem', etc... but the fill_source has columns 'x' and 'y'
your glyphs reference source as a source but you change and trigger event on fill_source.
All that could probably be fixed but there's a much easier way, adjust the range in the callback. E.g. replace your callback by this:
x_range = p.x_range
callback_test = CustomJS(args=dict(x_range=x_range), code="""
var start = cb_obj.value;
x_range.start = start;
x_range.change.emit();
""")
Note the change to the event trigger. Your version would work but I think it's going to be deprecated.
Also:
this line callback_test.args["sped"] = sped_slider is not necessary
you could add toolbar_location='above' in figure(...) to avoid rendering conflict with the legend
you're still going to have a layout problem between the slider and the legend which can be fixed in different ways (slider under or put the slider and the legend in a column before adding to the right of the plot, etc...)
I have the following bokeh code how do you add a horizontal line or glyph to the chart dynamically do I need a callback?
In the documentation http://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html it says "Custom callbacks like these can be set using a CustomJS object and passing it as the callback argument to a Widget object."
So do I set the call back before or after the update...
source = ColumnDataSource(dict(
time=[], average=[], low=[], high=[], open=[], close=[],
ma=[], macd=[], macd9=[], macdh=[], color=[]
))
#main chart
p = figure(plot_height=500, tools="xpan,xwheel_zoom,xbox_zoom,reset,crosshair,hover", x_axis_type=None, y_axis_location="right")
p.x_range.follow = "end"
p.x_range.follow_interval = 100
p.x_range.range_padding = 0
p.axis.minor_tick_in = -2
p.axis.minor_tick_out = 5
p.segment(x0='time', y0='low', x1='time', y1='high', line_width=2, color='black', source=source)
p.segment(x0='time', y0='open', x1='time', y1='close', line_width=8, color='color', source=source)
...
#count()
def update(t):
open, high, low, close, average = _get_prices(t)
color = "green" if open < close else "red"
new_data = dict(
time=[t],
open=[open],
high=[high],
low=[low],
close=[close],
color=[color],
)
print(open)
source.stream(new_data, 300)
output_notebook()
curdoc().add_periodic_callback(update, 50)
curdoc().title = "OHLC"