I created a form with two drop down menu's:
My goal is to make one dropdown dependent on the other dropdown.
This picture illustrates my goal and the current situation.
The sample code below can be run in Google Colab or Jupyter notebook to replicate the current situation.
##title
import ipywidgets as widgets
from ipywidgets import HBox, Label
from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider
import time
import pandas as pd
#Create DF
df = df = pd.DataFrame(columns = ['Dropdown_column', 'Float_column'])
df
# Layout
form_item_layout = Layout(
display='flex',
flex_flow='row',
justify_content='space-between',
)
button_item_layout = Layout(
display='flex',
flex_flow='row',
justify_content='center',
padding = '5%'
)
# Independent dropdown item
drop_down_input = 'Dropdown_input_1'
drop_down = widgets.Dropdown(options=('Dropdown_input_1', 'Dropdown_input_2'))
def dropdown_handler(change):
global drop_down_input
print('\r','Dropdown: ' + str(change.new),end='')
drop_down_input = change.new
drop_down.observe(dropdown_handler, names='value')
# Dependent drop down
# Dependent drop down elements
dependent_drop_down_elements = {}
dependent_drop_down_elements['Dropdown_input_1'] = ['A', 'B']
dependent_drop_down_elements['Dropdown_input_2'] = ['C', 'D', 'E']
# Define dependent drop down
dependent_drop_down = widgets.Dropdown(options=(dependent_drop_down_elements['Dropdown_input_1']))
def dropdown_handler(change):
global drop_down_input
print('\r','Dropdown: ' + str(change.new),end='')
drop_down_input = change.new
drop_down.observe(dropdown_handler, names='value')
# Button
button = widgets.Button(description='Add row to dataframe')
out = widgets.Output()
def on_button_clicked(b):
global df
button.description = 'Row added'
time.sleep(1)
with out:
new_row = {'Dropdown_column': drop_down_input, 'Float_column': float_input}
df = df.append(new_row, ignore_index=True)
button.description = 'Add row to dataframe'
out.clear_output()
display(df)
button.on_click(on_button_clicked)
# Form items
form_items = [
Box([Label(value='Independent dropdown'),
drop_down], layout=form_item_layout),
Box([Label(value='Dependent dropdown'),
dependent_drop_down], layout=form_item_layout)
]
form = Box(form_items, layout=Layout(
display='flex',
flex_flow='column',
border='solid 1px',
align_items='stretch',
width='30%',
padding = '1%'
))
display(form)
display(out)
Is this possible? If so, can you explain how?
You can do this by setting a if statement in your dropdown_handler function that checks if the value chosen in your independent dropdown is Dropdown_input_1 or Dropdown_input2. Still in the dropdown_handler, you can then modify accordingly the options arguments to either dependent_drop_down_elements['Dropdown_input_1'] or dependent_drop_down_elements['Dropdown_input_2'].
See code below:
import ipywidgets as widgets
from ipywidgets import HBox, Label
from ipywidgets import Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider
import time
import pandas as pd
#Create DF
df = df = pd.DataFrame(columns = ['Dropdown_column', 'Float_column'])
df
# Layout
form_item_layout = Layout(
display='flex',
flex_flow='row',
justify_content='space-between',
)
button_item_layout = Layout(
display='flex',
flex_flow='row',
justify_content='center',
padding = '5%'
)
# Independent dropdown item
drop_down_input = 'Dropdown_input_1'
drop_down = widgets.Dropdown(options=('Dropdown_input_1', 'Dropdown_input_2'))
# Dependent drop down
# Dependent drop down elements
dependent_drop_down_elements = {}
dependent_drop_down_elements['Dropdown_input_1'] = ['A', 'B']
dependent_drop_down_elements['Dropdown_input_2'] = ['C', 'D', 'E']
# Define dependent drop down
dependent_drop_down = widgets.Dropdown(options=(dependent_drop_down_elements['Dropdown_input_1']))
def dropdown_handler(change):
global drop_down_input
print('\r','Dropdown: ' + str(change.new),end='')
drop_down_input = change.new
#If statement checking on dropdown value and changing options of the dependent dropdown accordingly
if change.new=='Dropdown_input_2':
dependent_drop_down.options=dependent_drop_down_elements['Dropdown_input_2']
elif change.new=='Dropdown_input_1':
dependent_drop_down.options=dependent_drop_down_elements['Dropdown_input_1']
drop_down.observe(dropdown_handler, names='value')
# Button
button = widgets.Button(description='Add row to dataframe')
out = widgets.Output()
def on_button_clicked(b):
global df
button.description = 'Row added'
time.sleep(1)
with out:
new_row = {'Dropdown_column': drop_down_input, 'Float_column': float_input}
df = df.append(new_row, ignore_index=True)
button.description = 'Add row to dataframe'
out.clear_output()
display(df)
button.on_click(on_button_clicked)
# Form items
form_items = [
Box([Label(value='Independent dropdown'),
drop_down], layout=form_item_layout),
Box([Label(value='Dependent dropdown'),
dependent_drop_down], layout=form_item_layout)
]
form = Box(form_items, layout=Layout(
display='flex',
flex_flow='column',
border='solid 1px',
align_items='stretch',
width='30%',
padding = '1%'
))
display(form)
display(out)
As an example, this is what the output looks like when you pick Dropdown_input_2 on the first widget:
Related
I am trying to add a new row to an AgGrid Table using streamlit and python
At this point, I just want to add 1 or more new rows to the table generated by the AgGrid by pressing the "add row" button.
After pressing the "add row" button I generate a second table with the new row mistakenly, so I get 2 data-tables instead of updating the main table.
The initial data df = get_data() is been gathered from a SQL query. I want to add a new row and (for now) save it into a CSV file or at least get the updated DF with the new row added as an output and graph it
My current code
import streamlit as st
from metrics.get_metrics import get_data
from metrics.config import PATH_SAMPLES
filename: str = 'updated_sample.csv'
save_path = PATH_SAMPLES.joinpath(filename)
def generate_agrid(data: pd.DataFrame):
gb = GridOptionsBuilder.from_dataframe(data)
gb.configure_default_column(editable=True) # Make columns editable
gb.configure_pagination(paginationAutoPageSize=True) # Add pagination
gb.configure_side_bar() # Add a sidebar
gb.configure_selection('multiple', use_checkbox=True,
groupSelectsChildren="Group checkbox select children") # Enable multi-row selection
gridOptions = gb.build()
grid_response = AgGrid(
data,
gridOptions=gridOptions,
data_return_mode=DataReturnMode.AS_INPUT,
update_on='MANUAL', # <- Should it let me update before returning?
fit_columns_on_grid_load=False,
theme=AgGridTheme.STREAMLIT, # Add theme color to the table
enable_enterprise_modules=True,
height=350,
width='100%',
reload_data=True
)
data = grid_response['data']
selected = grid_response['selected_rows']
df = pd.DataFrame(selected) # Pass the selected rows to a new dataframe df
return grid_response
def onAddRow(grid_table):
df = pd.DataFrame(grid_table['data'])
column_fillers = {
column: (False if df.dtypes[column] == "BooleanDtype"
else 0 if df.dtypes[column] == "dtype('float64')"
else '' if df.dtypes[column] == "string[python]"
else datetime.datetime.utcnow() if df.dtypes[column] == "dtype('<M8[ns]')"
else '')
for column in df.columns
}
data = [column_fillers]
df_empty = pd.DataFrame(data, columns=df.columns)
df = pd.concat([df, df_empty], axis=0, ignore_index=True)
grid_table = generate_agrid(df)
return grid_table
# First data gather
df = get_data()
if __name__ == '__main__':
# Start graphing
grid_table = generate_agrid(df)
# add row
st.sidebar.button("Add row", on_click=onAddRow, args=[grid_table])
Here is a sample minimal code.
import streamlit as st
import pandas as pd
from st_aggrid import AgGrid, GridOptionsBuilder, GridUpdateMode
def generate_agrid(df):
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_selection(selection_mode="multiple", use_checkbox=True)
gridoptions = gb.build()
grid_response = AgGrid(
df,
height=200,
gridOptions=gridoptions,
update_mode=GridUpdateMode.MANUAL
)
selected = grid_response['selected_rows']
# Show the selected row.
if selected:
st.write('selected')
st.dataframe(selected)
return grid_response
def add_row(grid_table):
df = pd.DataFrame(grid_table['data'])
new_row = [['', 100]]
df_empty = pd.DataFrame(new_row, columns=df.columns)
df = pd.concat([df, df_empty], axis=0, ignore_index=True)
# Save new df to sample.csv.
df.to_csv('sample.csv', index=False)
def get_data():
"""Reads sample.csv and return a dataframe."""
return pd.read_csv('sample.csv')
if __name__ == '__main__':
df = get_data()
grid_response = generate_agrid(df)
st.sidebar.button("Add row", on_click=add_row, args=[grid_response])
Initial output
Output after pressing add row
sample.csv
team,points
Lakers,120
Celtics,130
I have already created AgGrid by loading data from a csv file. I am adding rows one by one via an external button. But when I try to edit the line I added, it disappears. I would be very grateful if you could help me where the error is. The codes are as follows.
import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridUpdateMode, JsCode
from st_aggrid.grid_options_builder import GridOptionsBuilder
import sys
import os
import altair as alt
from streamlit.runtime.legacy_caching import caching
def data_upload():
df = pd.read_csv("data.csv")
return df
if 'grid' in st.session_state:
grid_table = st.session_state['grid']
df = pd.DataFrame(grid_table['data'])
df.to_csv(“data.csv”, index=False)
else:
df = data_upload()
gd = GridOptionsBuilder.from_dataframe(df)
gd.configure_column("Location", editable=True)
gd.configure_column("HourlyRate", editable=True)
gd.configure_column("CollaboratorName", editable=True)
gridOptions = gd.build()
button = st.sidebar.button("Add Line")
if "button_state" not in st.session_state:
st.session_state.button_state = False
if button or st.session_state.button_state:
st.session_state.button_state = True
data = [['', '', 0]]
df_empty = pd.DataFrame(data, columns=['CollaboratorName', 'Location', "HourlyRate"])
df = pd.concat([df, df_empty], axis=0, ignore_index=True)
df.to_csv(“data.csv”, index=False)
gd= GridOptionsBuilder.from_dataframe(df)
grid_table = AgGrid(df,
gridOptions=gridOptions,
fit_columns_on_grid_load=True,
height=500,
width='100%',
theme="streamlit",
key= 'unique',
update_mode=GridUpdateMode.GRID_CHANGED,
reload_data=True,
allow_unsafe_jscode=True,
editable=True
)
if 'grid' not in st.session_state:
st.session_state['grid'] = grid_table
else:
grid_table_df = pd.DataFrame(grid_table['data'])
grid_table_df.to_csv(“data.csv”, index=False)
You can see the running app from here enter image description here
This one has a different approach but the goal could be the same.
Two radio buttons are created, if value is yes new line will be created, if value is no there is no new line.
If you want to add a new line, select yes and then add your entry. Then press the update button in the sidebar.
If you want to edit but not add a new line, select no, edit existing entry and then press the update button.
Code
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder
import pandas as pd
def data_upload():
df = pd.read_csv("data.csv")
return df
def show_grid(newline):
st.header("This is AG Grid Table")
df = data_upload()
if newline == 'yes':
data = [['', '', 0]]
df_empty = pd.DataFrame(data, columns=['CollaboratorName', 'Location', "HourlyRate"])
df = pd.concat([df, df_empty], axis=0, ignore_index=True)
gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_default_column(editable=True)
grid_table = AgGrid(
df,
height=400,
gridOptions=gb.build(),
fit_columns_on_grid_load=True,
allow_unsafe_jscode=True,
)
return grid_table
def update(grid_table):
grid_table_df = pd.DataFrame(grid_table['data'])
grid_table_df.to_csv('data.csv', index=False)
# start
addline = st.sidebar.radio('Add New Line', options=['yes', 'no'], index=1, horizontal=True)
grid_table = show_grid(addline)
st.sidebar.button("Update", on_click=update, args=[grid_table])
That happened because of your if button: statement. Streamlit button has no callbacks so any user entry under a st.button() will always reload the page so you end up losing the data, to prevent this, you can either initialize a session state fo your button or you can use st.checkbox() in place of st.button().
In this case I am going to fix your code by initializing a session state of the button.
def data_upload():
df = pd.read_csv("data.csv")
return df
st.header("This is AG Grid Table")
if 'grid' in st.session_state:
grid_table = st.session_state['grid']
df = pd.DataFrame(grid_table['data'])
df.to_csv('data.csv', index=False)
else:
df = data_upload()
gd = GridOptionsBuilder.from_dataframe(df)
gd.configure_column("Location", editable=True)
gd.configure_column("HourlyRate", editable=True)
gd.configure_column("CollaboratorName", editable=True)
gridOptions = gd.build()
def update():
caching.clear_cache()
button = st.sidebar.button("Add Line")
# Initialized session states # New code
if "button_state" not in st.session_state:
st.session_state.button_state = False
if button or st.session_state.button_state:
st.session_state.button_state = True # End of new code
data = [['', '', 0]]
df_empty = pd.DataFrame(data, columns=['CollaboratorName', 'Location', "HourlyRate"])
df = pd.concat([df, df_empty], axis=0, ignore_index=True)
gd= GridOptionsBuilder.from_dataframe(df)
df.to_csv('data.csv', index=False)
gridOptions = gd.build()
grid_table = AgGrid(df,
gridOptions=gridOptions,
fit_columns_on_grid_load=True,
height=500,
width='100%',
theme="streamlit",
key= 'unique',
update_mode=GridUpdateMode.GRID_CHANGED,
reload_data=True,
allow_unsafe_jscode=True,
editable=True
)
if 'grid' not in st.session_state:
st.session_state['grid'] = grid_table
grid_table_df = pd.DataFrame(grid_table['data'])
grid_table_df.to_csv('data.csv', index=False)
I think your code should work fine now with regards to the button issue.
Plotly: How to display and filter a dataframe with multiple dropdowns?
dataset = https://community.tableau.com/s/question/0D54T00000CWeX8SAL/sample-superstore-sales-excelxls
Hey, I am new to plotly as well, Following up on this question. My case is similar but with extra conditions. I need to plot the total sales for every quarter in every region filtered by "year" and "categories". I was able to reproduce the line chart in the article. But failed to reproduce a bar chart. There are 4 quarters in a year and 4 regions. Thus, at least 16 bars must be present at all times.
This is what I am trying to build:
enter image description here
import plotly.graph_objs as go
import pandas as pd
import numpy as np
file = pd.read_excel(r"Sample - Superstore.xlsx")
sales = file[['Sales','Region', 'Order Date','Category', 'State']]
sales["Quarters"] = sales['Order Date'].apply(lambda x: x.quarter)
sales["Years"] = sales['Order Date'].apply(lambda x: x.year)
df = sales.groupby(['Years','Quarters', 'Region', 'Category'], as_index = False).sum()
df_input = df.copy()
years = df['Years'].unique().tolist()
categories = df['Category'].unique().tolist()
regions = df['Region'].unique().tolist()
quarters = df['Quarters'].unique().tolist()
dfs = {}
for year in years:
dfs[year]=pd.pivot_table(df[df['Years']==year],
values='Sales',
index=['Quarters','Region'],
columns=['Category'],
aggfunc=np.sum)
# find row and column unions
common_cols = []
common_rows = []
for df in dfs.keys():
common_cols = sorted(list(set().union(common_cols,list(dfs[df]))))
common_rows = sorted(list(set().union(common_rows,list(dfs[df].index))))
df_common = pd.DataFrame(np.nan, index=common_rows, columns=common_cols)
# reshape each dfs[df] into common dimensions
dfc={}
for df_item in dfs:
#print(dfs[unshaped])
df1 = dfs[df_item].copy()
s=df_common.combine_first(df1)
df_reshaped = df1.reindex_like(s)
dfc[df_item]=df_reshaped
# plotly start
fig = go.Figure()
# for year in all_years:
# df2 = group_sales.loc[group_sales["Years"] == year]
# all_quarters =list(sorted(set(df2["Quarters"].astype(str))))
# all_regions =list(sorted(set(df2["Region"].astype(str))))
# fig.add_trace(go.Bar(x= all_quarters, y=df2.loc[df2["Region"] == all_regions[0]]["Sales"], name=all_regions[0],marker_color='blue', visible=(year== default_year)))
# fig.add_trace(go.Bar(x= all_quarters, y= df2.loc[df2["Region"] == all_regions[1]]["Sales"], name=all_regions[1], marker_color='lightblue', visible=( year == default_year)))
# fig.add_trace(go.Bar(x= all_quarters, y= df2.loc[df2["Region"] == all_regions[2]]["Sales"],name= all_regions[2],marker_color='grey', visible=(year== default_year)))
# fig.add_trace(go.Bar(x= all_quarters, y= df2.loc[df2["Region"] == all_regions[3]]["Sales"],name=all_regions[3], marker_color='red', visible=(year== default_year)))
# year_plot_names.extend([year]*4)
print(common_cols)
for col in common_cols:
# fig.add_trace(go.Bar(x= all_quarters, y=df2.loc[df2["Region"] == all_regions[0]]["Sales"], name=all_regions[0],marker_color='blue', visible=(year== default_year)))
fig.add_trace(go.Bar(x= quarters, name= regions[0],marker_color='blue', visible= True))
fig.add_trace(go.Bar(x= quarters, name= regions[1],marker_color='lightblue', visible= True))
fig.add_trace(go.Bar(x= quarters, name= regions[2],marker_color='grey', visible= True))
fig.add_trace(go.Bar(x= quarters, name= regions[3],marker_color='red', visible= True))
# fig.add_trace(go.Bar(x= regions,marker_color='blue', visible= True))
# fig.add_trace(go.Scatter(x=regions,
# visible=True,
# marker=dict(size=12, line=dict(width=2)),
# marker_symbol = 'diamond',name=col
# )
# )
fig.show()
# menu setup
updatemenu= []
# buttons for menu 1, names
buttons=[]
# create traces for each color:
# build argVals for buttons and create buttons
for df in dfc.keys():
argList = []
for col in dfc[df]:
temp = []
j = 0
# for i in range(0,4):
# temp2 = []
# for i in range(0,4):
# temp2.append(dfc[df][col].values[j])
# j+=1
# temp.append(temp2)
# argList.append(temp)
print(dfc[df][col])
argList.append(dfc[df][col].values)
argVals = [ {'y':argList}]
buttons.append(dict(method='update',
label=df,
visible=True,
args=argVals))
print(buttons)
# buttons for menu 2, colors
b2_labels = common_cols
# matrix to feed all visible arguments for all traces
# so that they can be shown or hidden by choice
b2_show = [list(b) for b in [e==1 for e in np.eye(len(b2_labels))]]
buttons2=[]
buttons2.append({'method': 'update',
'label': 'All',
'args': [{'visible': [True]*len(common_cols)}]})
# create buttons to show or hide
for i in range(0, len(b2_labels)):
buttons2.append(dict(method='update',
label=b2_labels[i],
args=[{'visible':b2_show[i]}]
)
)
# add option for button two to hide all
buttons2.append(dict(method='update',
label='None',
args=[{'visible':[False]*len(common_cols)}]
)
)
# some adjustments to the updatemenus
updatemenu=[]
your_menu=dict()
updatemenu.append(your_menu)
your_menu2=dict()
updatemenu.append(your_menu2)
updatemenu[1]
updatemenu[0]['buttons']=buttons
updatemenu[0]['direction']='down'
updatemenu[0]['showactive']=True
updatemenu[1]['buttons']=buttons2
updatemenu[1]['y']=0.6
fig.update_layout(showlegend=False, updatemenus=updatemenu)
fig.update_layout(yaxis=dict(range=[0,df_input['Sales'].max()+0.4]))
# title
fig.update_layout(
title=dict(
text= "<i>Filtering with multiple dropdown buttons</i>",
font={'size':18},
y=0.9,
x=0.5,
xanchor= 'center',
yanchor= 'top'))
# button annotations
fig.update_layout(
annotations=[
dict(text="<i>Year</i>", x=-0.4, xref="paper", y=1.1, yref="paper",
align="left", showarrow=False, font = dict(size=16, color = 'steelblue')),
dict(text="<i>Category</i>", x=-0.4, xref="paper", y=0.7, yref="paper",
align="left", showarrow=False, font = dict(size=16, color = 'steelblue')
)
])
fig.show()
I tried to do this with plotly buttons however getting two buttons to work together with a statically defined menu is challenging
it becomes very simple if dash is used with plotly as now there is callback capability that makes requirement simple.
import pandas as pd
import itertools
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
# Load Data
dfraw = pd.read_excel("Sample - Superstore.xls")
df = (dfraw.loc[:,['Sales','Region', 'Order Date','Category', 'State']]
.assign(Quarters=dfraw["Order Date"].dt.quarter,
Years=dfraw["Order Date"].dt.year)
.groupby(['Years','Quarters', 'Region', 'Category'], as_index=False).sum()
)
# add in totals with category "All"
df = (pd.concat([df, df.groupby(["Years","Quarters","Region"], as_index=False).agg({"Sales":"sum"}).assign(Category="All")])
.set_index(['Years', 'Region', 'Category','Quarters'])
.unstack("Region")
.droplevel(0, axis=1)
)
# colors...
config = {'Central':"blue", 'East':"lightblue", 'South':"grey", 'West':"red"}
fig = go.Figure()
# create a trace for every year, category & region
for y,cat in itertools.product(df.index.get_level_values("Years").unique(),df.index.get_level_values("Category").unique()):
dff = df.loc[(y,cat)]
for region in dff.columns:
fig.add_trace(go.Bar(x=dff.index, y=dff[region], name=region, meta=f"{y}{cat}", marker_color=config[region],
visible=(y==2014 and cat=="All"),
text=dff[region].apply(lambda v: f"{v/10**3:.0f}k"),textposition="outside")
)
# Build App
app = JupyterDash(__name__)
app.layout = html.Div([
dcc.Graph(id='graph'),
html.Label(["Year",dcc.Dropdown(id='year-dropdown', clearable=False,
value='2014', options=[{'label': year, 'value': year}
for year in df.index.get_level_values("Years").unique()])
]),
html.Label([
"Category",dcc.Dropdown(id='cat-dropdown', clearable=False, value='All',
options=[{'label': year, 'value': year}
for year in df.index.get_level_values("Category").unique()])
]),
])
# Define callback to update graph
#app.callback(
Output('graph', 'figure'),
[Input("year-dropdown", "value"),Input("cat-dropdown", "value")]
)
def update_figure(year, cat):
return fig.update_traces(visible=False).update_traces(visible=True, selector={"meta":f"{year}{cat}"}).update_layout(title=f"{year} {cat}")
# Run app and display result inline in the notebook
app.run_server(mode='inline')
I'm attempting to put together a Jupyter Notebook that allows a user to input a state in a dropdown, which would change the result in the second dropdown to only show cities from that state. Once the city is selected, a button could be pressed to refresh the graph (I realize there's you can use manual with interact, but was unable to get this to function properly) and show the resultant data (covid cases over time). I've had some success making the widgets, but I can't get the data to plot correctly.
This is my first time using widgets in Jupyter and I'm a bit lost what with interact, interactivity, display, and observe (not to mention the deprecated on_trait_change). Here's what I have so far...
from ipywidgets import interact, widgets
from bqplot import pyplot as plt
import pandas as pd
#make example DF
date_list = ['2020-02-01','2020-02-01','2020-02-01','2020-02-01','2020-02-02','2020-02-02','2020-02-02','2020-02-02']
state_list = ['CA','NY','CA','NY','CA','NY','CA','NY']
city_list = ['San Fran','NYC','LA','Albany','San Fran','NYC','LA','Albany']
cases_list = [0,0,0,0,2,6,4,8]
df = pd.DataFrame(index=date_list)
df['state'] = state_list
df['city'] = city_list
df['cases'] = cases_list
#getting unique state and city list sorted in alphabetical order
state_unique = df['state'].unique()
state_unique.sort()
state = widgets.Dropdown(
options=['All'] + list(state_unique),
value='CA', #indicates default starting value
description='State:', #this is the label for the dropdown
)
city = widgets.Dropdown(
description='City:',
)
# function that updates 'city' dropdown depending on value in 'state' dropdown
def list_cities(x):
if state.value == 'All':
city.options = ['']
return city_list
else:
temp = ['All'] + sorted(df.loc[df['state'].eq(state.value),'city'].unique())
city.options = temp
state.observe(list_cities, names = 'value') #the names part tells the observe the change name to look for, this is only looking for value changes
b_refresh = widgets.Button(
description='Refresh',
icon='fa-refresh',
button_style='warning',
layout=widgets.Layout(width='100px')
)
# plotting function
def set_plot(x_data,y_data):
plt.plot(x_data, y_data)
plt.show()
x = list(df.index.unique())
y = df.loc[\
(df['city']==city.value)&\
(df['state']==state.value),'cases']
b_refresh.on_click(set_plot(x, y))
display (state,city,b_refresh)
from ipywidgets.widgets import Dropdown, Button
from ipywidgets.widgets import Layout, HBox, VBox
import bqplot as bq
import pandas as pd
#make example DF
date_list = ['2020-02-01','2020-02-01','2020-02-01','2020-02-01','2020-02-07','2020-02-07','2020-02-07','2020-02-07']
state_list = ['CA','NY','CA','NY','CA','NY','CA','NY']
city_list = ['San Fran','NYC','LA','Albany','San Fran','NYC','LA','Albany']
cases_list = [0,10,0,12,2,6,4,8]
df = pd.DataFrame(index=date_list)
df['state'] = state_list
df['city'] = city_list
df['cases'] = cases_list
df.index = pd.to_datetime(df.index)
df.rename_axis("date", axis='index', inplace=True)
#getting unique state list sorted in alphabetical order
state_unique = df['state'].unique()
state_unique.sort()
#make graphical parts - 2 dropdowns and refresh button
state = Dropdown(
options=['All'] + list(state_unique),
value='All', #indicates default starting value
description='State:', #this is the label for the dropdown
layout=Layout(width='200px',
margin = '0px 0px 0px 0px')
)
city = Dropdown(
description='City:',
layout=Layout(width='200px',
margin = '0px 0px 0px 0px')
)
b_refresh = Button(
description='Refresh',
icon='fa-refresh',
button_style='warning',
layout=Layout(width='100px',
margin = '0px 0px 0px 60px')
)
#make graph parts
x = list(df.index.unique())
y = list(df.groupby('date')['cases'].sum())
xs = bq.DateScale()
ys = bq.LinearScale(min=0)
line = bq.Lines(
x=x,
y=y,
scales={'x': xs, 'y': ys}
)
x_ax = bq.Axis(
scale=xs,
label='Dates',
grid_lines='solid',
tick_format='%m-%d',
tick_rotate=-0,
label_location ='middle',
label_offset = "40px"
)
y_ax = bq.Axis(
scale=ys,
orientation='vertical',
tick_format=',d',
label='FillInLater',
grid_lines='solid',
label_offset = "60px"
)
# function that updates 'city' dropdown depending on value in 'state' dropdown
def list_cities(x):
if state.value == 'All':
global y
city.options = ['']
y = list(df.groupby('date')['cases'].sum())
else:
temp = ['All'] + sorted(df.loc[df['state'].eq(state.value),'city'].unique())
city.options = temp
update_city(x)
# function that updates graph's y values depending on value in 'city' dropdown
def update_city(x):
global y
if city.value == 'All':
matching_cities = df.loc[(df['state']==state.value),'cases'].to_frame(name='cases')
y = list(matching_cities.groupby('date')['cases'].sum())
else:
y = list(df.loc[\
(df['city']==city.value)&\
(df['state']==state.value),'cases'])
# update plot function
def set_plot(b):
global y
line.y = [y]
#assign widget actions to functions
state.observe(list_cities, names = 'value') #the names part tells .observe the change name to look for, this is only looking for value changes
city.observe(update_city, names = 'value')
b_refresh.on_click(set_plot)
#display all
fig = bq.Figure(
layout=Layout(width='95%', height='400px', border ='solid 1px gray'),
axes=[x_ax, y_ax],
marks=[line],
fig_margin=dict(top=10, bottom=80, left=80, right=20),
animation_duration=500
)
box = HBox(
children=(state, city, b_refresh),
layout=Layout(margin = '10px 0px 10px 0px')
)
vert_box = VBox(
children=(box, fig),
layout=Layout(border='solid 1px gray', margin = '0 0 0 0')
)
display (vert_box)
Hi I'm fairly new to Python, Plotly and Jupyter Notebook. I would like to use a slider to select the number of days as the range in a query to which a graph is created from. My only issue is that I want the graph to automatically update on interaction with the slider, without having to re-run the query and graph creation. My code is below:
slider = widgets.IntSlider()
display(slider)
sliderVal = slider.value
df = pd.read_sql(f"""
SELECT CASE WHEN SiteID LIKE 3 THEN 'BLAH'
WHEN SiteID LIKE 4 THEN 'BLAHBLAH'
END AS Website,
COUNT(1) AS Count
FROM viewName
WHERE (TimeStamp > DATEADD(DAY, -{sliderVal}, GETDATE()))
GROUP BY SiteId
ORDER BY Count DESC
""", conn)
data = [go.Bar(x=df.Website, y=df.Count)]
layout = go.Layout(
xaxis=dict(
title='Website'),
yaxis=dict(
title='Exception count'),
title=f'Number of exceptions per user in the last {sliderVal} days')
chart = go.Figure(data=data, layout=layout, )
py.iplot(chart, filename='WebExceptions')
Thanks in advance!
If you do not want to rerun the query, then your data frame df must contain the results for all the values that you want the intslider widget to take, the function linked to the widget will then simply filter the data and redraw the graph with the new filtered data.
Here's an example with some dummy data:
import ipywidgets as widgets
import plotly.offline as py
import plotly.graph_objs as go
import pandas as pd
py.init_notebook_mode(connected = True)
# Dummy data, to be replaced with your query result for the range of sliderVal
df = pd.DataFrame({'Days': [1] * 3 + [2] * 4 + [3] * 5,
'Website': [1,2,3, 4,5,6,7, 8,9,10,11,12],
'Count': [10,5,30, 15,20,25,12, 18,17,30,23,27]})
def update_plot(sliderVal):
filtered_df = df.query('Days== ' + str(sliderVal))
data = [go.Bar(x = filtered_df.Website,
y = filtered_df.Count)]
layout = go.Layout(
xaxis = dict(title = 'Website'),
yaxis = dict(title = 'Exception count'),
title = f'Number of exceptions per user in the last {sliderVal} days')
chart = go.Figure(data = data, layout = layout, )
py.iplot(chart, filename = 'WebExceptions')
# links an IntSlider taking values between 1 and 3 to the update_plot function
widgets.interact(update_plot, sliderVal = (1, 3))
and here is the result with sliderVal = 2: