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:
Related
I'm trying to understand how to create an interactive widget with Bokeh. The goal is to have the TextInput box change the x value in the code, and in turn alter the position of the dot on the graph.
If someone could help me out with an example and describe what I should be learning in order to achieve this would be greatly appreciated!
from bokeh.plotting import *
from bokeh.models import *
from bokeh.io import *
from bokeh.transform import *
from bokeh.layouts import *
import numpy as np
x = 1
y = 5
def x_funtion(x):
x_value = x*4
return x_value
number = x_funtion(x)
def handler(attr, old, new):
global number
number = x_funtion(new)
return number
text_input = TextInput(value=str(x), title="x")
text_input.on_change("value", handler)
p =figure()
p.circle(number,y)
curdoc().title = "Hello, world!"
curdoc().add_root(row(p,text_input))
There are different ways of handling that, but in the long run it's better to use a ColumnDataSource. In general, when you want to update something that's managed by Bokeh, you want to change already existing Bokeh models.
from bokeh.layouts import *
from bokeh.models import *
from bokeh.plotting import *
def x_function(x):
x_value = x * 4
return x_value
x_init = 1
ds = ColumnDataSource(data=dict(x=[x_function(x_init)], y=[5]))
def handler(attr, old, new):
try:
new = int(new)
except ValueError:
pass
else:
ds.data['x'] = [x_function(new)]
text_input = TextInput(value=str(x_init), title="x")
text_input.on_change("value", handler)
p = figure()
p.circle('x', 'y', source=ds)
curdoc().title = "Hello, world!"
curdoc().add_root(row(p, text_input))
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"
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?
I am trying to produce a dashboard like interactions for my bar chart using callback function without using bokeh serve functionality. Ultimately, I would like to be able to change the plot if any of the two drop-down menus is changed. So far this only works when threshold value is hard-coded. I only know how to extract cb_obj value but not from dropdown that is not actually called. I have looked at this and this answer to formulate first attempt.
Here is my code:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
def callback(source=source, count_source = count_source, error_source=error_source, window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = cb_obj.value
threshold = 20
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="12", options=list(count_data.Threshold.astype(str).unique()))
exp_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)
The solution to the question is that Select menu needs to be defined before callback function. This code works:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="20", options=list(count_data.Threshold.astype(str).unique()))
def callback(source=source, count_source = count_source, error_source=error_source, exp_dropdown = exp_dropdown,
thr_dropdown=thr_dropdown,window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = exp_dropdown.value
threshold = thr_dropdown.value
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown.callback = CustomJS.from_py_func(callback)
thr_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)
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"