Bokeh plot not updating - python

I would love some help, I'm going in circles here. I know I'm doing something stupid but my plot isn't updating. I can't debug to see if my filter function isn't working or there's a problem that my inputs for the plot aren't linked the dynamic source input. Since even the starting plot doesn't take the initialized parameters I think it's something there. PS- any advice on having a select all, including all in the categorical choices for the select boxes would be amazing too.
Cheers,
Tom
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook, curdoc
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel, Div
from bokeh.models.widgets import (CheckboxGroup, Slider, Select, TextInput, RangeSlider, Tabs, CheckboxButtonGroup, TableColumn, DataTable, Select)
from bokeh.layouts import layout, column, row, Widgetbox
from bokeh.layouts import widgetbox, row, column
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
weather = pd.read_csv('YYYYYY.csv', dayfirst=True, parse_dates=True, index_col=[1], encoding = "ISO-8859-1")
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size = '20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
p = figure(plot_height=600, plot_width=700, title="'2018'", toolbar_location="below", tooltips=TOOLTIPS)
p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
p = style(p)
return p
TOOLTIPS=[
("Name", "#Target"),
("$", "#Round"),
("Country", "#CC")
]
def get_dataset(deal_num, ccstring, descstring, vertstring):
df_filter = weather[weather['USD_Base'] >=(deal_num) & weather['CC'].str.contains(ccstring) & weather['Description'].str.contains(descstring) & weather['Vertical Market'].str.contains(vertstring)]
return ColumnDataSource(df_filter)
def update_plot(attr, old, new):
deal_num = int(deal_select.value)
ccstring = str(cc_select.value)
descstring = str(description_select.value)
vertstring = str(vert_select.value)
new_src = get_dataset(deal_num, ccstring, descstring, vertstring)
src.data.update(new_src.data)
# Create Input controls
deal_select = Slider(title="$ Invested", value=0, start=0, end=200, step=2)
cclist = weather["CC"].unique().tolist()
cc_select = Select(title="Country Name:", options= cclist, value='GB')
description_select = TextInput(title="Company description contains")
vertlist = weather["Vertical Market"].unique().tolist()
vert_select = Select(title="Vertical:", options= ['All'] + vertlist, value='None')
controls = widgetbox(deal_select, cc_select, description_select, vert_select)
deal_select.on_change('value', update_plot)
cc_select.on_change('value',update_plot)
description_select.on_change('value',update_plot)
vert_select.on_change('value',update_plot)
# Make the deal data source
src = get_dataset(deal_num = deal_select.value,
ccstring = cc_select.value,
descstring = description_select.value,
vertstring = vert_select.value)
# Make the deal plot
p = make_plot(src)
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child=layout, title = '2018')
# Put all the tabs into one application
tabs = Tabs(tabs = [tab])
# Put the tabs in the current document for display
curdoc().add_root(tabs)

If you are updating a glyph, you need to change the datasource for that glyph directly. In your case, you should assign the circle glyph to a variable, such as:
circle = p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
Then in your update_plot(attr, old, new) function try this:
circle = p.select_one({'name':'circle'})
circle.data_source.data = new_src.data
For selecting all, possibly the MultiSelect Widget would work?

Related

Bokeh - Vertical layout of plots

I am trying to show two plots vertically. I get an error message at show(column(west_fig, east_fig))
Could you please help me understand the error message?
Version: BokehJS 1.4.0 successfully loaded.
Note: I am running this code in Jupyter notebook
## Detailed error message:
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call
last) in
8
9 # Plot the two visualizations in a vertical configuration
---> 10 show(column(west_fig, east_fig))
RuntimeError: Models must be owned by only a single document,
DaysTicker(id='2330', ...) is already in a doc
# Bokeh libraries
from bokeh.io import output_file, output_notebook
from bokeh.plotting import figure, show
from bokeh.layouts import column
# output to notebook
output_notebook()
# Plot the two visualizations in a vertical configuration
show(column(west_fig, east_fig))
####### west_fig #######
# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file, output_notebook
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Output to notebook
output_notebook()
# Convert `stDate` column to datetime column
west_top_2['stDate'] = pd.to_datetime(west_top_2['stDate'], format = '%Y-%m-%d')
# Create a ColumnDataSource
west_cds = ColumnDataSource(west_top_2)
# Create views for each team
rockets_view = CDSView(source = west_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'HOU')]
)
warriors_view = CDSView(source = west_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'GS')]
)
# Create and configure the figure
west_fig = figure(x_axis_type = 'datetime',
plot_height = 500,
plot_width = 600,
title = 'Western Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label = 'Date',
y_axis_label = 'Wins',
toolbar_location = None
)
# Render the race as step lines
west_fig.step('stDate', 'gameWon', source = west_cds, view = rockets_view, color = '#CE1141', legend = 'Rockets')
west_fig.step('stDate', 'gameWon', source = west_cds, view = warriors_view, color = '#006BB6', legend = 'Warriors')
# Move the legend to the upper top-left corner
west_fig.legend.location = "top_left"
# Show the plot
show(west_fig)
####### east_fig #########
import pandas as pd
# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file, output_notebook
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Output to notebook
output_notebook()
# Convert stDate to datetime column
standings['stDate'] = pd.to_datetime(standings['stDate'], format = '%Y-%m-%d')
# Create a ColumnDataSource
standings_cds = ColumnDataSource(standings)
# Create views for each team
celtics_view = CDSView(source=standings_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'BOS')]
)
raptors_view = CDSView(source=standings_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'TOR')]
)
# Configure Figure object
east_fig = figure(x_axis_type = 'datetime',
plot_height = 500,
plot_width = 600,
title = 'Eastern Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label = 'Date',
y_axis_label = 'Wins',
toolbar_location = None
)
# Render the race as step lines
east_fig.step('stDate', 'gameWon', color = '#007A33', legend = 'Celtics',
source = standings_cds, view = celtics_view)
east_fig.step('stDate', 'gameWon', color = '#CE1141', legend = 'Raptors',
source = standings_cds, view = raptors_view)
# Move the legend to the upper left hand corner
east_fig.legend.location = "top_left"
# Show the plot
show(east_fig)
Make only one show statement at the end of your code. This implies deleting show(column(west_fig, east_fig)), show(west_fig), show(east_fig) lines and adding at the end of the code show(column(west_fig, east_fig))

bokeh button callback only partially working

I am plotting a complex interactive donut chart in bokeh. The code below is a simplification of a component of this chart.
I have a function which compiles a dataframe of data for the donut, and then converts it to a CDS. This data is then plotted as annular wedges.
A radiobutton group should trigger a switch to a different dataframe (as CDS), and replot the annular wedge glyphs.
The example provide (for Jupyter Lab) works in one direction. When initially plotted the button.active == 0 (outer_1). When outer_2 is selected, the chart correctly changes to plot the second dataframe (cds).
But when the outer_1 button is pressed, the glyphs do not change back. The callback is triggered - as the title changes. But the glyphs do not change.
Why are the glyphs not changing in subsequent button presses / callbacks?
I've read a few similar SO posts, and also reviewed a number of bokeh examples (the weather example here is similar)
import pandas as pd
from math import pi
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.models.widgets import RadioButtonGroup
from bokeh.io import curdoc
from bokeh.models.sources import ColumnDataSource
from bokeh.layouts import column, row
from bokeh.plotting import show, output_notebook
output_notebook()
df = pd.DataFrame({'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1.5, pi+1.5, (3*pi/2)+1.5],
'inner': [100,100,100],
'outer': [200,200,200],
'color':['red','green','blue']})
df_2 = pd.DataFrame({'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1, pi+1, (3*pi/2)+1],
'inner': [100,100,100],
'outer': [250,300,350],
'color':['orange','gray','purple']})
data_1 = ColumnDataSource(data=df)
data_2 = ColumnDataSource(data=df_2)
def create_doc(doc):
button = RadioButtonGroup(labels=["outer_1", "outer_2"], active=0)
inputs = column(button)
p = figure(plot_width=600, plot_height=600, title="data_1",
x_axis_type=None, y_axis_type=None,
x_range=(-300, 300), y_range=(-300, 300),
min_border=0, outline_line_color=None,
background_fill_color='white', toolbar_location="above")
circle = p.circle(0,0, radius=100, fill_alpha=0, line_color='grey', line_alpha=0.4)
source = [data_1, data_2][button.active]
segments = p.annular_wedge(0,0,'inner', 'outer', 'start', 'end', color='color', alpha=0.6, source=source, name='segments')
r = row (inputs,p)
def callback(attr, old, new):
if button.active == 1:
p.title.text = 'data_2 {}'.format(button.active)
source.data.update(data_2.data)
elif button.active == 0:
p.title.text = 'data_1 {}'.format(button.active)
source.data.update(data_1.data)
button.on_change('active', callback)
doc.add_root(r)
show(create_doc)
The glpyhs change successfully once, but not again, although the changing title text (on button presses) indicate that the buttons & callback continue to work partially.
Why converting to DataFrame knowing that internally the data of ColumnDataSource is a dictionary? The following code works fine for Bokeh v1.1.0
from math import pi
from bokeh.plotting import figure, show, curdoc, Row, Column, output_notebook
from bokeh.models import RadioButtonGroup
output_notebook()
data1 = {'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1.5, pi+1.5, (3*pi/2)+1.5],
'inner': [100,100,100],
'outer': [200,200,200],
'color':['red','green','blue']}
data2 = {'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1, pi+1, (3*pi/2)+1],
'inner': [100,100,100],
'outer': [250,300,350],
'color':['orange','gray','purple']}
def create_doc(doc):
button = RadioButtonGroup(labels=["outer_1", "outer_2"], active=0)
p = figure(plot_width=600, plot_height=600, title="data_1",
x_axis_type=None, y_axis_type=None,
x_range=(-300, 300), y_range=(-300, 300),
min_border=0, outline_line_color=None,
background_fill_color='white', toolbar_location="above")
circle = p.circle(0,0, radius=100, fill_alpha=0, line_color='grey', line_alpha=0.4)
segments = p.annular_wedge(0,0,'inner', 'outer', 'start', 'end', color='color', alpha=0.6, source = data1, name='segments')
def callback(attr, old, new):
if button.active == 0:
print '000'
p.title.text = 'data_1 {}'.format(button.active)
segments.data_source.data = data1
elif button.active == 1:
print '111'
p.title.text = 'data_2 {}'.format(button.active)
segments.data_source.data = data2
button.on_change('active', callback)
inputs = Column(button)
r = Row(inputs,p)
doc.add_root(r)
show(create_doc)

Bokeh figure doesn't updates on dropdown.on_change

I have some problem with updating Bokeh plot. It's simple piece of code, one figure with one curve and one dropdown, which can changes time period, 7,10 and 30 days. When i change dropdown value, nothing happens.
I already have gone through various articles, but i didn't find clear answer for me.
Code example is presented below.
Thanks
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Dropdown
from pandas_datareader import data
import datetime
TIME_PERIOD = 30
def get_data(period):
today = datetime.date.today()
timedelta = datetime.timedelta(days=period)
start = today - timedelta
df = data.DataReader(name="BTC-USD", data_source="yahoo", start=start)
dates = df.loc[str(start):str(today)].index
y = df["Volume"]
data1 = dict(
xaxis=dates,
yaxis=y
)
source = ColumnDataSource(data1)
return source
def update_date(attr, old, new):
global TIME_PERIOD
temp = new
TIME_PERIOD = int(temp)
def get_plot(data_source):
p = figure(title="Cryptocurrencies volumes", x_axis_label="Дни", y_axis_label="Volume 24hr",
x_axis_type="datetime")
p.line(x="xaxis", y="yaxis", color="green", source=data_source)
return p
dropdown_menu = [("7","7"),("10","10"),("30","30")]
dropdown = Dropdown(label="Выбор временного интервала",button_type="success",menu=dropdown_menu, value="30")
dropdown.on_change("value", update_date)
data1 = get_data(TIME_PERIOD)
plot = get_plot(data1)
image = row(dropdown,plot)
curdoc().add_root(image)
curdoc().title = "Plot"
Simply setting your time period is not enough. You have to call the get_data() function again and set the data of the ColumnDataSource it returns as the data of the ColumnDataSource that is used by your line glyph.
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Dropdown
from pandas_datareader import data
import datetime
TIME_PERIOD = 30
def get_data(period):
today = datetime.date.today()
timedelta = datetime.timedelta(days=period)
start = today - timedelta
df = data.DataReader(name="BTC-USD", data_source="yahoo", start=start)
dates = df.loc[str(start):str(today)].index
y = df["Volume"]
data1 = dict(
xaxis=dates,
yaxis=y
)
source = ColumnDataSource(data1)
return source
def update_date(attr, old, new):
TIME_PERIOD = int(new)
newdata = get_data(TIME_PERIOD)
source.data = newdata.data
dropdown_menu = [("7","7"),("10","10"),("30","30")]
dropdown = Dropdown(label="Выбор временного интервала",button_type="success",menu=dropdown_menu, value="30")
dropdown.on_change("value", update_date)
source = get_data(TIME_PERIOD)
p = figure(title="Cryptocurrencies volumes", x_axis_label="Дни", y_axis_label="Volume 24hr",
x_axis_type="datetime")
p.line(x="xaxis", y="yaxis", color="green", source=source)
image = row(dropdown,p)
curdoc().add_root(image)
curdoc().title = "Plot"

Changing colors on bokeh patches plot real time

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.

Bokeh Title Spacing

i'm looking for a way to reduce the spacing in a Bokeh plot, this is a HeatMap that I've made in Bokeh:
I want the title to be more close to the plot but I haven't found the way to do this. ¿Any ideas on how to solve this?
Here is my code:
from bokeh.io import output_file
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LinearColorMapper
)
from bokeh.plotting import figure
output_file('test.html', mode='inline')
source = ColumnDataSource(RandomData)
TOOLS = "hover,save"
# Creating the Figure
HM = figure(title="HeatMap",
x_range=[str(i) for i in range(1,32)],
y_range=[str(i) for i in range(1643,1600,-1)]+[str(i) for i in range(6043,6000,-1)],
x_axis_location="above", plot_width=500, plot_height=970,
tools=TOOLS, toolbar_location='right')
# Figure Styling
HM.grid.grid_line_color = None
HM.axis.axis_line_color = None
HM.axis.major_tick_line_color = None
HM.axis.major_label_text_font_size = "7pt"
HM.axis.major_label_text_alpha = 0.5
HM.axis.major_label_standoff = 0
HM.toolbar.logo = None
HM.title.text_font = 'century gothic'
HM.title.text_font_size = '14pt'
HM.title.text_font_style = 'normal'
HM.title.text_color = '#6a94d8'
HM.title_location = 'below'
HM.title.align = 'center'
# Color Mapping
mapper = LinearColorMapper(palette='Blues9', low=RandomData.Total.max(),
high=RandomData.Total.min())
# Creating the Glyphs
HM.rect(x='XData', y="YData", width=1, height=1,source=source,
fill_color={'field': 'Total','transform': mapper},line_alpha=0.05)
show(HM)

Categories