Updating hbar_stack in Bokeh Server - python

I am trying to update the bars in a Bokeh server application. I am already doing this in a line chart like this:
p.line(x="x", y="y1", source=self.source01, line_width=2, line_alpha=1.0
via updating the source's dictionary within a function like this:
self.source01.data = dict(
x=self.df_update['timestamp_h'],
y1=self.df_update[dropdown_aux_surp.value])
but this does not seem to work for an hbar_stack which I create like this:
accu_result_Qaux_therm = 0
acc_result_Qaux_el = 0
acc_result_Q45 = 0
acc_result_Q65 = 0
acc_result_Q85 = 0
self.categories = ['Qaux_therm', 'Qaux_el', ' Q45', 'Q65', 'Q85']
self.exports_source = ColumnDataSource(data=dict(categories=[], Berechnete=[]))
self.exports_dict = {'categories': self.categories ,
'Berechnete': [ accu_result_Qaux_therm, acc_result_Qaux_el, acc_result_Q45, acc_result_Q65, acc_result_Q85]}
self.exports_source.data = self.exports_dict
p_1 = figure(y_range=self.categories, plot_height=250, x_range=(-16, 16),
title="Kumulierte Jahreswerte",
toolbar_location=None)
p_1.hbar_stack(status, y='categories', height=0.9, color='red',
source=self.exports_source.data,
legend=['Legende'])
if try to update the hbar_stack's dict to load new values into the graph, nothing happens:
accu_result_Qaux_therm = 10
acc_result_Qaux_el = 20
acc_result_Q45 = 44
acc_result_Q65 = 5
acc_result_Q85 = 6
self.exports_dict = {'categories': self.categories,
'Berechnete': [accu_result_Qaux_therm, acc_result_Qaux_el, acc_result_Q45,
acc_result_Q65, acc_result_Q85]}
self.exports_source.data = self.exports_dict
I apologize if this can be achieved more efficiently but I am new to Python and Bokeh and just need some enlightenment or information if and how I need to update the hbar_stack differently.

I am very sorry, I solved this by myself. I overlooked the following:
There is a typo: Instead of
p_1.hbar_stack(status, y='categories', height=0.9, color='red',
source=self.exports_source.data,
legend=['Legende'])
it needs to read:
p_1.hbar_stack(status, y='categories', height=0.9, color='red',
source=self.exports_source,
legend=['Legende'])
I.e. instead of referencing the ColumnDataSource Object self.exports_source I had been referencing the dictionary self.exports_source.data. Changing this showed the desired result and the graph was updating.

Related

Bokeh / Python issue with TOOLTIPS / Hover over

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.

Updating Data in Bokeh

I'm new to Bokeh and not sure how to get my plot data and how to update it.
My code is following these instructions:
https://github.com/WillKoehrsen/Bokeh-Python-Visualization/blob/master/interactive/exploration/interactive_development.ipynb
But unfurtunately the update method doesn't seem to work for me. i was trying to find documentation regarding this method but couldn't find any. can anyone assist?
basically i've generated a pandas dataframe from my data and converted it to ColumnDataSource. now i want to add or subtract data from it but the new ColumnDataSource does not update the old one.
Any help would be most appreciated! This is my code as of now, it still won't update properly:
def update(attr, old, new):
stations_to_plot = [int(station_selection.labels[i]) for i in station_selection.active]
by_station = pd.DataFrame(data=no_bikes.loc[stations_to_plot,:'23 PM'].values,index=list(map(str,initial_stations))
,columns=no_bikes.loc[:,:'23 PM'].columns.tolist())
new_src = ColumnDataSource(by_station.T)
r.data_source.data.update(new_src.data)
stations=list(map(str,no_bikes.loc[:,'station_avg'].nlargest(10).index.tolist()))
station_selection = CheckboxGroup(labels=stations, active = [0,1,3])
station_selection.on_change('active', update)
initial_stations = [int(station_selection.labels[i]) for i in station_selection.active]
range_select = RangeSlider(start = 0, end = 23, value = (0, 23),step = 1, title = 'Hours to plot')
range_select.on_change('value', update)
by_station = pd.DataFrame(data=no_bikes.loc[initial_stations,:'23 PM'].values,index=list(map(str,initial_stations))
,columns=no_bikes.loc[:,:'23 PM'].columns.tolist())
src = ColumnDataSource(by_station.T)
p = figure(plot_width = 500, plot_height = 500, title = 'Chances not to find bikes',
x_axis_label = 'Hour of the day', y_axis_label = 'Proportion',x_range=src.data['index'])
for i,station in enumerate(src.data.keys()):
if station in list(map(str, initial_stations)):
r=p.line(x='index',y=station,source =src, legend='Station N.'+station, color=Category20_16[i], line_width=5)
controls = WidgetBox(station_selection,range_select)
layout = row(controls, p)
curdoc().add_root(layout)

Create time series graph with rangeslider

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...)

Why do I get the error, "350 is not JSON serializable"

I've been working on a project in Python using Matplotlib in tandem with Django. Right now I just want to display the graph in the webpage. Before, I saved the figure as a PNG, and the graph showed up. Unfortunately, this disables some interactive features that I had planned on including. In order to work around this, I used MPLD3's function, fig_to_html, which I saw worked in other examples. However, when I used the line in my own code, I got the error, "350 is not JSON serializable".
Here is my code:
def index(request):
# template = loader.get_template('')
noaaNmbr='11809'
#for right now, this is the only active region that I'm pulling data for. When I get the graph up and running, I will make it more interactive for the user so that he can
urlData = "http://www.lmsal.com/hek/hcr?cmd=search-events3&outputformat=json&instrument=IRIS&noaanum="+ noaaNmbr +"&hasData=true"
webUrl = urlopen(urlData)
counter = 0
data = webUrl.read().decode('utf-8')
hekJSON = json.loads(data)
getInfo(counter, hekJSON)
sort()
# Sorts the data from earliest to most recent
fig, ax = plt.subplots(figsize=(30, 30))
circle = Circle((0, 0), 980, facecolor='none', edgecolor=(0, 0.8, 0.8), linewidth=3, alpha=0.5)
ax.add_patch(circle)
# Creates circular representation of the sun on the graph because that's all I really need for now
plt.plot(xcen, ycen, color="red")
plt.plot(xcen, ycen, 'ro', color = 'blue')
#first plots the points in red, then draws lines between the points in blue
plt.xlim([setXMin(hekJSON), setXMax(hekJSON)])
plt.ylim([setYMin(hekJSON), setYMax(hekJSON)])
#sets the boundaries of the graph
ax.set_xticks(np.arange(round_multiple(setXMin(hekJSON),50), round_multiple(setXMax(hekJSON), 50), 50))
ax.set_yticks(np.arange(round_multiple(setYMin(hekJSON),50), round_multiple(setYMax(hekJSON), 50), 50))
#sets the ticks
for i in range(getNumberOfEntries(hekJSON)):
if xfov[i] != 0:
xStart = xcen[i] - xfov[i]/14
yStart = ycen[i] - yfov[i]/14
ax.add_patch(Rectangle((xStart, yStart), xfov[i]/7, yfov[i]/7, facecolor='none'))
plt.grid()
# texts = fixAnnotations(createAnnotations(hekJSON, noaaNmbr))
# adjust_text(texts, arrowprops=dict(arrowstyle="-", color='k', lw=0.5))
# canvas = FigureCanvasAgg(fig)
# response = HttpResponse(content_type='image/png')
# canvas.print_png(response)
# g = mpld3.fig_to_html(fig)
g = mpld3.display(fig)
return HttpResponse(g)
# return response
This is the section of code that uses JSON stuff:
noaaNmbr='11809'
#for right now, this is the only active region that I'm pulling data for. When I get the graph up and running, I will make it more interactive for the user so that he can
urlData = "http://www.lmsal.com/hek/hcr?cmd=search-events3&outputformat=json&instrument=IRIS&noaanum="+ noaaNmbr +"&hasData=true"
webUrl = urlopen(urlData)
counter = 0
data = webUrl.read().decode('utf-8')
hekJSON = json.loads(data)
getInfo(counter, hekJSON)
Can somebody please explain what the issue is and how to fix it?

Dynamic periodic update python bokeh

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"

Categories