How to change the drop down values dynamically - python

I am new to Bokeh and need some help please. I am trying change the drop-down-1 box values dynamically based on other drop-down-2 box selection. I looked at Bokeh examples but cant find one. Here is the code that I am messing around.
source = ColumnDataSource(data=dict(server_list=["old_value_1", "old_value_2"]))
def update():
tech_val = tech.value
if tech_val == 'art':
source.data = dict(
server_list=["new_value_1", "new_value_2"]
)
# servers.update()
env = Select(title="Environment", value="PROD", options=["DEV", "QA", "PROD"])
tech = Select(title="Subject Area", value="science", options=["science", "art"])
servers = Select(title="Server", options=source.data['server_list'])
controls = [env, tech, servers]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed'
inputs = widgetbox(*controls, sizing_mode=sizing_mode)
l = layout([[inputs]], sizing_mode=sizing_mode)
curdoc().add_root(l)
curdoc().title = "Sliders"

Here is an example which will change the options displayed in the Environment drop down depending on which value is selected in the Subject area drop down.
If you also want the values to change you can just use the same approach.
This should allow you to change values and options of drop downs dynamically.
from bokeh.layouts import column,row, widgetbox,layout
from bokeh.io import curdoc
from bokeh.models.widgets import (Select)
from bokeh.plotting import ColumnDataSource
source = ColumnDataSource(data=dict(server_list=["old_value_1", "old_value_2"]))
def update(attrname, old, new):
tval = tech.value
env.options = env_dict[tval]
tech_options = ["science", "art"]
env_options1 = ["DEV", "QA", "PROD"]
env_options2 = ["DEV2", "QA2", "PROD2"]
env_dict = dict(zip(tech_options,[env_options1, env_options2]))
env = Select(title="Environment", value="PROD", options=["DEV", "QA", "PROD"])
tech = Select(title="Subject Area", value="science", options=tech_options)
servers = Select(title="Server", options=source.data['server_list'])
""" update drop down 1 based off drop down 2 values """
tech.on_change("value", update)
sizing_mode = 'fixed'
inputs = widgetbox([env,tech,servers], sizing_mode=sizing_mode)
l = layout([[inputs]], sizing_mode=sizing_mode)
curdoc().add_root(l)
curdoc().title = "Sliders"

Related

How to get updated bokeh graph in streamlit (server issue?)

I want to embed a bokeh graphic in streamlit. the interactive bokeh graph is operational with the command boker serve --show application.
But when I integrate it into streamlit, I have the graph that appears but the updates with the tabs are broken.
Is there a possibility to run everything on streamlit, without going through a bokeh server?
Here is my code
import pandas as pd
from bokeh.layouts import column, row
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, show
df = pd.read_csv(r'C:\mypath\myfile.csv')
SIZES = list(range(6, 22, 3))
COLORS = Spectral5
N_SIZES = len(SIZES)
N_COLORS = len(COLORS)
columns = sorted(df.columns)
discrete = [x for x in columns if df[x].dtype == object]
continuous = [x for x in columns if x not in discrete]
def create_figure():
xs = df[x.value].values
ys = df[y.value].values
x_title = x.value.title()
y_title = y.value.title()
kw = dict()
if x.value in discrete:
kw['x_range'] = sorted(set(xs))
if y.value in discrete:
kw['y_range'] = sorted(set(ys))
kw['title'] = "%s vs %s" % (x_title, y_title)
p = figure(height=600, width=800, tools='pan,box_zoom,hover,reset', **kw)
p.xaxis.axis_label = x_title
p.yaxis.axis_label = y_title
if x.value in discrete:
p.xaxis.major_label_orientation = pd.np.pi / 4
sz = 9
if size.value != 'None':
if len(set(df[size.value])) > N_SIZES:
groups = pd.qcut(df[size.value].values, N_SIZES, duplicates='drop')
else:
groups = pd.Categorical(df[size.value])
sz = [SIZES[xx] for xx in groups.codes]
c = "#31AADE"
if color.value != 'None':
if len(set(df[color.value])) > N_COLORS:
groups = pd.qcut(df[color.value].values, N_COLORS, duplicates='drop')
else:
groups = pd.Categorical(df[color.value])
c = [COLORS[xx] for xx in groups.codes]
p.circle(x=xs, y=ys, color=c, size=sz, line_color="white", alpha=0.6, hover_color='white', hover_alpha=0.5)
return p
def update(attr, old, new):
layout.children[1] = create_figure()
x = Select(title='X-Axis', value='Distance (m)', options=columns)
x.on_change('value', update)
y = Select(title='Y-Axis', value='Jumps', options=columns)
y.on_change('value', update)
size = Select(title='Size', value='None', options=['None'] + continuous)
size.on_change('value', update)
color = Select(title='Color', value='None', options=['None'] + continuous)
color.on_change('value', update)
controls = column(x, y, color, size, width=200)
layout = row(controls, create_figure())
curdoc().add_root(layout)
curdoc().title = "AppAnalyse"
st.bokeh_chart(layout, use_container_width=True)
when I run this code on streamlit, I have the graph on streamlit but the updates do not work (see photo1)
streamlit graph ok but no update
then when I execute the command: boker serve --show application, from the console, I get the desired graph on a server, which is functional (see photo 2)
graph ok on bokeh serve
there is a possibility to integrate this graph with "st.bokeh_chart()" to get this graph in streamlit with functional updates?
Thank you very much for the help provided and excuse my English please :)

How to make my bokeh vbar interactive using Python?

I am trying to work on a POC model in which I can add some interactivity (like selection of the student based on which the vbar plot will change). I am using a basic student data with their marks.
The data is as follows:
Column 1
Name
Ayan
Deepa
Sayan
Shobhit
Column 2
Marks
98
96
92
94
What I am able to achieve with the below code:
I am able to create the functions and able to get the output in Bokeh Server output. I am also able to create a on_change call back which re creates the dataset based on user input in the dropdown selection.
Where I need help:
I am unable to update the source in my my plots. I tried various ways from various online sites but I am unable to do so.
Some issues faced are:
When I create the ColumnDataSource with the dataframe the output plot is coming blank
If I am using dataframe instead of ColumnDataSource the update function is showing it cant change a df or list
Code:
## Packages
from bokeh.core.properties import value
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import layout, column, gridplot, Row, widgetbox, row
from bokeh.models import TapTool, HoverTool, ColumnDataSource, Range1d, BoxSelectTool, LinearAxis, Range1d
from bokeh.models.widgets import Button, RadioButtonGroup, Select, Slider, CheckboxGroup, Panel, Tabs
from bokeh.models.annotations import LabelSet
_tools_to_show = 'box_zoom,pan,save,hover,resize,reset,tap,wheel_zoom'
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import datetime
## Creating Dataset
def make_dataset(Input):
global Piv_INCS2_CD_Plot3old
global Piv_INCS2_CD_Plot3
global Week_List
global P2_Major
global Total_Incidents_Created
global Resolution_SLO_Miss_Percent
global new_src
global Piv_INCS2_CD_Plot3_List
global Old
global Old_Filter
global Old_CDS
global Name
global Marks
global Names2
global Old_CDS_Name
global Old_CDS_Marks
print("select 2 =", select.value )
print("Input 2 =", Input)
Old = pd.read_csv('Check_Data.csv', encoding='ISO-8859-1')
Old_Filter = pd.DataFrame(Old[Old.Name == Input])
Old_Filter.to_csv('Old_Filter.csv')
Name = [Input]
print("Name = ", Name)
Names2 = Old_Filter["Name"].tolist()
Marks = Old_Filter["Marks"].tolist()
print("Names = ", Name )
print("Marks = ", Marks )
Old_CDS = ColumnDataSource(data = Old_Filter)
print("OLD_CDS = ", Old_CDS)
Old_CDS_Name = ColumnDataSource(data = {'Name':Name})
Old_CDS_Marks = ColumnDataSource(data = {'Marks': Marks})
return Old_CDS
## Creating Plot
def plot(Old_CDS):
global p3
p3 = figure(plot_height=630, plot_width=1000, title="Marks Trend",
toolbar_location=None, tools="")
p3.vbar(x = "Name", top = "Marks", width = 0.9, source=Old_CDS)
p3.xgrid.grid_line_color = None
p3.y_range.start = 0
return p3 # returns the plot
## On Change Function
def update(attr, old, new):
global Piv_INCS2_CD_Plot3_New
global Week_List_New
global Old_CDS_1
global p3
global lay
global Old_CDS_Name_2
Old_CDS_1 = make_dataset(select.value)
Old_CDS.data.update(Old_CDS_1.data)
## Selection Option
options=[("Ayan","Ayan"),("Deepa","Deepa")]
select=Select(title="Name",options=options)
print("select=", select.value )
## Changing value based on user input
select.on_change("value",update)
## Defining intial user selection
Initial_Input = "Ayan"
Old_CDS_2 = make_dataset(Initial_Input)
## Defining Layout
p3 = plot(Old_CDS_2)
lay = row(p3, select)
curdoc().add_root(lay)
Expected result: I should be able to view the vbar chart in the page and when I change the user from dropdown the plot changes
Here is your working code (for Bokeh v1.0.4). Changes:
x_range = Old_CDS.data['Name'] added
global Old_CDS_2
Old_CDS_2.data['Marks'] = Old_CDS_1.data['Marks'] You were updating Old_CDS instead of Old_CDS_2 which you passed to the vbars
from bokeh.core.properties import value
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import layout, column, gridplot, Row, widgetbox, row
from bokeh.models import TapTool, HoverTool, ColumnDataSource, Range1d, BoxSelectTool, LinearAxis, Range1d
from bokeh.models.widgets import Button, RadioButtonGroup, Select, Slider, CheckboxGroup, Panel, Tabs
from bokeh.models.annotations import LabelSet
_tools_to_show = 'box_zoom,pan,save,hover,resize,reset,tap,wheel_zoom'
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import datetime
import os
# # Creating Dataset
def make_dataset(Input):
global Piv_INCS2_CD_Plot3old
global Piv_INCS2_CD_Plot3
global Week_List
global P2_Major
global Total_Incidents_Created
global Resolution_SLO_Miss_Percent
global new_src
global Piv_INCS2_CD_Plot3_List
global Old
global Old_Filter
global Old_CDS
global Name
global Marks
global Names2
global Old_CDS_Name
global Old_CDS_Marks
print("select 2 =", select.value)
print("Input 2 =", Input)
Old = pd.read_csv(os.path.join(os.path.dirname(__file__), 'Check_Data.csv'), encoding = 'ISO-8859-1')
Old_Filter = pd.DataFrame(Old[Old.Name == Input])
Old_Filter.to_csv(os.path.join(os.path.dirname(__file__), 'Old_Filter.csv'), index = False)
select.options = [(name, name) for name in Old['Name'].values]
print options
Name = [Input]
print("Name = ", Name)
Names2 = Old_Filter["Name"].tolist()
Marks = Old_Filter["Marks"].tolist()
print("Names = ", Name)
print("Marks = ", Marks)
Old_CDS = ColumnDataSource(data = Old_Filter)
print("OLD_CDS = ", Old_CDS)
Old_CDS_Name = ColumnDataSource(data = {'Name': Name})
Old_CDS_Marks = ColumnDataSource(data = {'Marks': Marks})
return Old_CDS
# # Creating Plot
def plot(Old_CDS):
global p3
print Old_CDS.data
p3 = figure(plot_height = 630, plot_width = 1000, title = "Marks Trend", x_range = Old_CDS.data['Name'],
toolbar_location = None, tools = "")
p3.vbar(x = "Name", top = "Marks", width = 0.9, source = Old_CDS)
p3.xgrid.grid_line_color = None
p3.y_range.start = 0
return p3 # returns the plot
# # On Change Function
def update(attr, old, new):
global Piv_INCS2_CD_Plot3_New
global Week_List_New
global Old_CDS_1
global p3
global lay
global Old_CDS_Name_2
global Old_CDS_2
Old_CDS_1 = make_dataset(select.value)
Old_CDS_2.data['Marks'] = Old_CDS_1.data['Marks']
# # Selection Option
options = [("Ayan", "Ayan"), ("Deepa", "Deepa")]
select = Select(title = "Name", options = options)
print("select=", select.value)
# # Changing value based on user input
select.on_change("value", update)
# # Defining intial user selection
Initial_Input = "Ayan"
Old_CDS_2 = make_dataset(Initial_Input)
# # Defining Layout
p3 = plot(Old_CDS_2)
lay = row(p3, select)
curdoc().add_root(lay)
Result:

Bokeh plot not updating

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?

Unable to update datasource in Bokeh python

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

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.

Categories