Python Bokeh graph displays two lines when update function called - python

I've been working with the python bokeh function, and I wish to display a graph of a stock when the ticker is entered into the TextInput section. However, in my case the only way I've made this work is to create a new p.line within the update function, which overlays one stock graph on top of another. Is there a way to update my source data or update function such that a graph with only the input stock is shown?
p=figure(
height=400,
x_axis_type='datetime',
title=(company+' ('+tickerstring+') '),
tools='pan, box_zoom, wheel_zoom, reset',
)
p.line('x', 'y', source=source)
line1=p.line(thedates, stockcloseprices)
p.grid.grid_line_color="white"
p.xaxis.axis_label = 'Date'
p.yaxis.axis_label = 'Price'
p.add_tools(HoverTool(
tooltips=[
("Date", "#x{%F}"),
('Close',"#y")
],
formatters={
'x':'datetime', # use 'datetime' formatter for 'date' field
},
mode='vline'
))
source = ColumnDataSource(data=dict(
x=thedates,
y=stockcloseprices
))
div = Div(text='<br><b> Key Points </b><br><br>'+percentagechange+'<br><br>'+performance,
width=200, height=100)
def update(f):
fstocksymbol=str(f.upper())
if fstocksymbol in stocksymbols:
p.title.text = (symbolsdictionary[fstocksymbol]).upper()+' ('+fstocksymbol+')'
tickerstring=fstocksymbol
firstfunction=stockname(tickerstring)
secondfunction=stockdata(firstfunction)
stockdates=[]
stockcloseprices=[]
for value in secondfunction:
stockdates.append(value[0])
stockcloseprices.append(value[4])
thedates = np.array(stockdates, dtype=np.datetime64)
p.line(thedates, stockcloseprices)
push_notebook()
elif fstocksymbol=='':
print('')
else:
print("")
interact(update, f='')
grid = gridplot([p, div, button], ncols=2, plot_width=570, plot_height=400)
show(grid, notebook_handle=True)

There are several example notebooks that show how to update a data source for an existing glyph in the examples directory on GitHub:
https://github.com/bokeh/bokeh/tree/master/examples/howto/notebook_comms
In brief, you want to update the data source:
source.data = new_data_dict
push_notebook()

Related

Plotly Text Annotation shows the same text throughout

I am trying to show the text on the bars based on some filtered data but the text shows the same value for the last item in the list. I can't seem to find out what the issue is because display_texts itself gives me what I expect
filtered_data = df[df["Year"] == 2017]
display_texts = filtered_data["column_name"].tolist()
fig = px.bar(
filtered_data,
x="x_column",
y="y_column",
color="color",
title="some title",
)
fig.update_traces(
texttemplate=display_texts,
textposition="outside",
)
fig.update_layout(showlegend=False)
fig.update_layout(autosize=False, width=1800, height=600)
You should remove texttemplate=display_texts and add text=display_texts to px.bar.

Custom JS Callback for allowing only one circle to be shown on hovering in Circle plot in Bokeh

I am new to Bokeh I am trying to replicate the line plots shown in https://www.worldometers.info/coronavirus/ using Bokeh.Here is my full code
from datetime import datetime, timedelta, date
import requests
import json
from bokeh.plotting import output_notebook, figure, show
from bokeh.models import ColumnDataSource, HoverTool, Title
def DateFormatter(x):
s = '01222020'
global given_date
given_date = datetime(month=int(s[:2]), day=int(s[2:4]), year=int(s[4:]))
given_date += timedelta(days=x)
final = str(given_date.strftime('%m{}%d{}%y').format('/','/'))
if int(final[0:2])<10 and int(final[3:5])<10:
final = final[1:]
final = final[0 : 2 : ] + final[3 : :]
elif int(final[0:2])<10:
final = final[1:]
elif int(final[3:5])<10:
final = final[0 : 2 : ] + "/" + final[4 : :]
return final
def DateFormatterForPlot(x):
s_plot = '01222020'
global given_date_plot
given_date_plot = datetime(month=int(s_plot[:2]), day=int(s_plot[2:4]), year=int(s_plot[4:]))
given_date_plot += timedelta(days=x-1)
final_plot1 = str(given_date_plot.strftime('%b %d'))
if int(final_plot1[4:6])<10:
final_plot1 = final_plot1[0 : 4 : ] + final_plot1[5 : :]
return final_plot1
finallist=[]
l2 = []
plot_list = []
country = "India"
chart_type = "cases"
base_site = f'https://disease.sh/v3/covid-19/historical/{country}?lastdays=all'
r = requests.get(base_site)
if r.status_code == 200:
packages_json = r.json()
today_date = str(date.today().strftime('%m{}%d{}%y').format('/','/'))
if int(today_date[0:2])<10 and int(today_date[3:5])<10:
today_date = today_date[1:]
today_date = today_date[0 : 2 : ] + today_date[3 : :]
elif int(today_date[0:2])<10:
today_date = today_date[1:]
elif int(today_date[3:5])<10:
today_date = today_date[0 : 2 : ] + "/" + today_date[4 : :]
for i in range(1,1000):
current_date = DateFormatter(i)
if current_date==today_date:
f_date = datetime(2020, 1, 22)
delta = given_date - f_date
break;
for i in range(delta.days):#Day 157 is 26th June
try:
a = DateFormatter(i)
packages_str = json.dumps(packages_json['timeline'][chart_type][a], indent=2)
finallist.append(int(packages_str))
except KeyError:
pass
count = 0
for i in finallist:
count = count + 1
for i in range(1,count+1):
l2.append(i)
for i in l2:
plot_list.append(DateFormatterForPlot(i))
else:
print("Not a valid country")
source = ColumnDataSource(data=dict(y=finallist, x=l2, desc=plot_list))
TOOLTIPS = """
<style>
.bk-tooltip>div:not(:first-child) {display:none;}
</style>
<b>X: </b> #desc <br>
<b>Y: </b> #y{0,0}
"""
plot = figure(background_fill_color='#fafafa', x_axis_label='Days', y_axis_label='Coronavirus {}'.format(chart_type.capitalize()), plot_width=1200, plot_height=400, toolbar_location=None)
plot.line('x', 'y', source=source, legend = 'Number of {}'.format(chart_type.capitalize()), line_width=2, color='gray')
cr = plot.circle('x', 'y', size=10, source=source, fill_color="grey", hover_fill_color="gainsboro", fill_alpha=0.1, line_color=None, hover_line_color="white", hover_fill_alpha=1)
plot.add_tools(HoverTool(tooltips=TOOLTIPS, renderers=[cr]))
plot.add_layout(Title(text="Coronavirus {} Example Graph".format(chart_type.capitalize()), align="center"), "above")
plot.legend.location = 'top_left'
plot.left[0].formatter.use_scientific = False #Used to disable scientific notation on y-axis
The problem that is arising here is that more than one circle glyphs are being shown when cursor is hovered on the area of the plot where the density of circle glyphs are high.
I think there is a CustomJS callback for Hovertool to allow only one circle glyph to be shown on hovering mouse over it but I am not able to implement it.
Output of the snippet can be seen here http://geetanshjindal.pythonanywhere.com/charts/
I am using the latest bokeh version 2.1.1 and python version 3.6
I think you have three options and all have to do with de-densifying your plot:
Reduce the size of the circle glyphs so that they don't overlap. size=5 seems to work OK for your plot width.
Increase plot width so that the circle glyphs are more spaced out. Of course, this will add a scroll bar so might not be the best user experience.
Don't remove the Bokeh toolbar and encourage the user to interact with the plot - zooming in to see all circle glyphs.
The approach of selectively showing tooltips using CSS is interesting, but it hides the decision-making ("show the tooltip only for the first circle of the overlapping two") from the user, potentially causing confusion.
One alternative approach would be to remove the tooltips altogether and add annotations in key places instead (passing a million of cases?), as well as putting a table with all dates and values as a side panel for reference.
For annotations, you can use a combination of a Label and Bezier curve models:
from bokeh.models import Label, Bezier
test_label = Label(x=50, y=550000, x_units='data', y_units='data',
text='Custom description of the datapoint', render_mode='css',
border_line_color='white', border_line_alpha=1.0,
border_line_width=3, background_fill_color='white',
background_fill_alpha=1.0)
# play around with control point values for best effect
curve_source = ColumnDataSource(dict(
x0=[150],
y0=[395048],
x1=[130],
y1=[600000],
cx0=[150],
cy0=[500000],
cx1=[150],
cy1=[600000]
)
)
test_curve = Bezier(
x0="x0", y0="y0", x1="x1", y1="y1",
cx0="cx0", cy0="cy0", cx1="cx1", cy1="cy1",
line_color="green", line_width=1)
plot.add_glyph(curve_source, test_curve)
plot.add_layout(test_label)

the slider do not responce

I want to use the slider to change the figures, but it is no use. I think maybe the problem is in the Callback part. but i have no idea how to do it.
month = [1,2,3,1,2,3,1,2,3,1,2,3]
tilts = [1,1,1,2,2,2,3,3,3,4,4,4]
data = [0.1,0.2,0.3,1,2,3,11,12,13,21,22,24]
df = pd.DataFrame({'month':month,'tilt':tilts,'data'=data})
df_default = df[df['tilt']==1]
source = ColumnDataSource({
'x': df_default.month.tolist(),
'y': df_default.data.tolist(),
})
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
var tilt = slider.value;
var x = data['x']
var y = data['y']
'x' = df[df['tilt']==tilt].month.tolist();
'y' = df[df['tilt']==tilt].data;
plot.line(x='x', y='y', source=source, line_width=3,
line_alpha=0.6,
);
source.change.emit();
""")
slider = Slider(start=1, end=4, step=1, value=1, title='tilt')
slider.js_on_change('value',callback)
layout = row(
plot,
column(slider)
)
output_file("slider.html", title="slider.py example")
show(layout)
it can show , but apparently, the callback is not working
There are several problems with this code, one of them is that these lines are nonsensical:
'x' = df[df['tilt']==tilt].month.tolist();
'y' = df[df['tilt']==tilt].data;
These are trying to assign values to string constants, which is invalid JavaScript. However, a bigger issue is that you are trying to use Pandas DataFrames in a CustomJS callback, and this can never work. Pandas DataFrames are a Python object, they do not exist inside browsers. To run real Python code, e.g. use Pandas DataFrames, you will have to create and run a Bokeh Server application.

Python bokeh slider not refreshing plot

I am creating a bokeh plot with a slider to refresh plot accordingly. There are 2 issues with the code posted.
1. The plot is not refreshed as per the slider. Please help in providing a fix for this issue.
2. Plot is not displayed with curdoc() when bokeh serve --show fn.ipynb is used
I'm trying to visualise this CSV file.
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool, Slider
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
#Creating Column Data Source
source = ColumnDataSource({
'x' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==2001].loc[(['ABC']), :].index.get_level_values('District_Name')
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
#xmin, xmax = min(data.Crop_Year), max(data.Crop_Year)
#ymin, ymax = min(data.Production), max(data.Production)
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Area,
'y' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].Production,
'state' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('State_Name'),
'district' : crop_dt[crop_dt.index.get_level_values('Year')==yr].loc[(['ABC']), :].index.get_level_values('District_Name')
}
source.data = new_data
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
layout = row(widgetbox(slider), p)
curdoc().add_root(layout)
show(layout)
Also tried a different option using CustomJS as below, but still no luck.
callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
var yr = slider.value;
var x = data['x']
var y = data['y']
'x' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Area;
'y' = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['ABC']), :].Production;
p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district');
}
source.change.emit();
""")
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
yr_slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year', callback=callback)
callback.args["slider"] = yr_slider
Had a lot of issues trying to execute your code and I have changed some things, so feel free to correct me if did something wrong.
The error was caused by the creation of the ColumnDataSource, I had to change the level value to Crop_Year instead of Year. The loc 'ABC' also caused an error so I removed that too (And I had to add source = ColumnDataSource({, you probably forgot to copy that)
I also added a dropdown menu so it's possible to only show the data from one district.
Also, I'm not quite sure if it's possible to start a bokeh server by supplying a .ipynb file to --serve. But don't pin me down on that, I never use notebooks. I've tested this with a .py file.
#!/usr/bin/python3
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, CategoricalColorMapper, HoverTool
from bokeh.plotting import figure, curdoc
from bokeh.palettes import viridis
from bokeh.layouts import row, widgetbox
from bokeh.models.widgets import Select, Slider
#Importing and processing data file
crop = pd.read_csv('crop_production.csv')
#Cleaning Data
crop.fillna(np.NaN)
crop['Season'] = crop.Season.str.strip()
#Removing Whitespace #Filtering the dataset by Season
crop_season = crop[crop.Season == 'Whole Year']
crop_dt = crop_season.groupby(['State_Name', 'District_Name', 'Crop_Year']).mean().round(1)
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==2001]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')=='Tamil Nadu']
#Creating Column Data Source
source = ColumnDataSource({
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
})
#Creating color palette for plot
district_list = crop_dt.loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
# Creating the figure
p = figure(
title = 'Crop Area vs Production',
x_axis_label = 'Area',
y_axis_label = 'Production',
plot_height=900,
plot_width=1200,
tools = [HoverTool(tooltips='#district')]
)
glyphs = p.circle(x='x', y='y', source=source, size=12, alpha=0.7,
color=dict(field='district', transform=color_mapper),
legend='district')
p.legend.location = 'top_right'
def update_plot(attr, old, new):
#Update glyph locations
yr = slider.value
state = select.value
crop_dt_year = crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr]
crop_dt_year_state = crop_dt_year[crop_dt_year.index.get_level_values('State_Name')==state]
new_data = {
'x': crop_dt_year_state.Area.tolist(),
'y': crop_dt_year_state.Production.tolist(),
'state': crop_dt_year_state.index.get_level_values('State_Name').tolist(),
'district': crop_dt_year_state.index.get_level_values('District_Name').tolist()
}
source.data = new_data
#Update colors
district_list = crop_dt.loc[([state]), :].index.get_level_values('District_Name').unique().tolist()
call_colors = viridis(len(district_list))
color_mapper = CategoricalColorMapper(factors=district_list, palette=call_colors)
glyphs.glyph.fill_color = dict(field='district', transform=color_mapper)
glyphs.glyph.line_color = dict(field='district', transform=color_mapper)
#Creating Slider for Year
start_yr = min(crop_dt.index.get_level_values('Crop_Year'))
end_yr = max(crop_dt.index.get_level_values('Crop_Year'))
slider = Slider(start=start_yr, end=end_yr, step=1, value=start_yr, title='Year')
slider.on_change('value',update_plot)
#Creating drop down for state
options = list(set(crop_dt.index.get_level_values('State_Name').tolist()))
options.sort()
select = Select(title="State:", value="Tamil Nadu", options=options)
select.on_change('value', update_plot)
layout = row(widgetbox(slider, select), p)
curdoc().add_root(layout)
#Jasper Thanks a lot. This works, however it doesnt work with .loc[(['Tamil Nadu']), :]. Reason for having this is to filter the data by adding a bokeh dropdown or radio button object and refresh the plot based on the filters. The below code works only if .loc[(['Tamil Nadu']), :] is removed. Is there any other way to fix this please?
def update_plot(attr, old, new):
yr = slider.value
new_data = {
'x' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Area.tolist(),
'y' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].Production.tolist(),
'state' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('State_Name').tolist(),
'district' : crop_dt[crop_dt.index.get_level_values('Crop_Year')==yr].loc[(['Tamil Nadu']), :].index.get_level_values('District_Name').tolist()
}
source.data = new_data

Bokeh - Update func for Slider

I have some problem with updating the plot values with Bokeh. Select and Slider don't change the plot. The code is supposed to plot 'budget' along with 'vote_average' in different years. Slider is for showing data (release_date) from 1970 to 2016 years. I'm working in the Jupyter notebook. Code is below:
source = ColumnDataSource(data = {
'x': movies.budget,
'y': movies.vote_average,
'revenue': movies.revenue,
'profit': movies.profit,
'original_title': movies.original_title,
'release_date': movies.release_date
})
p = figure(x_axis_label='Budget in millions $', y_axis_label='Rank',
tools = [HoverTool(tooltips = '#original_title')])
p.circle(x = 'x', y = 'y', source=source)
def update_plot(attr, old, new):
yr = slider.value
# Set new_data
new_data = {
'x' : data.budget.loc[data.release_date == str(yr)].values,
'y' : data.vote_average.loc[data.release_date == str(yr).values
}
# Assign new_data to source.data
source.data = new_data
slider = Slider(start=1970, end=2016, step=1, value=1970, title='Year')
slider.on_change('value', update_plot)
layout = row(widgetbox(slider), p)
show(layout)
What's supposed to be in 'update plot' function? It seems that this func just doesn't work.
Binding widgets in Jupyter notebook requires custom Javascript callbacks as far as I know. Your example would only work on a bokeh serve app. Check out this notebook to see how.

Categories

Resources