Streamlit Statefulness for Multi-Submit - python

I’m trying to compare two charts side by side that can be sourced from different datasets with the same structure.
This is what it looks like:
This is what the code looks like:
col1, col2 = st.columns(2)
#Left Column
with col1:
dataset1 = st.selectbox(
'Pick Type',
datasets)
df1 = pd.read_csv(os.path.join(".\\data\\",dataset1+".csv"), index_col=False)
option1 = st.selectbox(
'Pick Flight',
df1['log_id_full'].unique(),
key='option1')
col1_choice = list(df1.columns)
col1_choice.remove('timestamp')
columns1 = st.multiselect(
'Pick Values',
col1_choice,
key='col1'
)
if 'df1_filtered' not in st.session_state:
st.session_state.df1_filtered = df1.loc[df1['log_id_full'] == option1]
form1= st.form(key='form1')
submit1 = form1.form_submit_button('Submit')
if submit1:
st.line_chart(
st.session_state.df1_filtered,
x = 'timestamp',
y = columns1)
#Right column
with col2:
dataset2 = st.selectbox(
'Pick Type',
datasets,
key = 'dataset2')
df2 = pd.read_csv(os.path.join(".\\data\\",dataset2+".csv"), index_col=False)
option2 = st.selectbox(
'Pick Data',
df2['log_id_full'].unique(),
key = 'option2')
col2_choice = list(df2.columns)
col2_choice.remove('timestamp')
columns2 = st.multiselect(
'Pick Values',
col2_choice,
key='col2'
)
if 'df2_filtered' not in st.session_state:
st.session_state.df2_filtered = df2.loc[df2['log_id_full'] == option2]
form2 = st.form(key='form2')
submit2 = form2.form_submit_button('Submit')
if submit2:
st.line_chart(
st.session_state.df2_filtered,
x = 'timestamp',
y = columns2)
If I remove the submit button one chart always shows an error until it gets values selected so I basically just need each column to operate independently and preserve state.
The above is how I tried to add stateful-ness based on the documentation examples. But it still behaves such that when I submit one it reruns the app and removes the line chart.
I know there are other optimizations, best-practice improvements I can make but this app is just for a POC and I was hoping to demo this functionality

Remove the form and just replace it with checkbox.
submit1 = st.checkbox(...)
submit2 = st.checkbox(...)

Related

Vertical scrollbar in FLET app, does not appear

I am building a FLET app, but sometimes I have a datatable which is too large for the frame it is in. For the row I have a scrollbar appearing, but for the column I just don't seem to get it working.
In this code a scrollbar simply does not appear.
import pandas as pd
pd.options.display.max_columns = 100
from services.bag import PCHN
from utils.convertors import dataframe_to_datatable
import flet as ft
def main(page: ft.page):
def bag_service(e):
pc = '9351BP' if postal_code_field.value == '' else postal_code_field.value
hn = '1' if house_number_field.value == '' else house_number_field.value
address = PCHN(pc,
hn).result
bag_container[0] = dataframe_to_datatable(address)
page.update() # This is not updating my bag_table in place though. It stays static as it is.
# define form fields
postal_code_field = ft.TextField(label='Postal code')
house_number_field = ft.TextField(label='House number')
submit_button = ft.ElevatedButton(text='Submit', on_click=bag_service)
# fields for the right column
address = PCHN('9351BP', '1').result
bag_table = dataframe_to_datatable(address)
bag_container = [bag_table]
# design layout
# 1 column to the left as a frame and one to the right with two rows
horizontal_divider = ft.Row
left_column = ft.Column
right_column = ft.Column
# fill the design
page.add(
horizontal_divider(
[left_column(
[postal_code_field,
house_number_field,
submit_button
]
),
right_column(
[
ft.Container(
ft.Row(
bag_container,
scroll='always'
),
bgcolor=ft.colors.BLACK,
width=800,)
],scroll='always'
)
]
)
)
if __name__ == '__main__':
ft.app(target=main,
view=ft.WEB_BROWSER,
port=666
)
I am lost as to what could be the case here. Any help would be much appreciated.

Python, Streamlit AgGrid add new row to AgGrid Table

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

How to Edit Cell of Streamlit AgGrid's Row?

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.

Receiving error: _tkinter.TclError: invalid command name ".!frame3.!treeview"

I am writing a simple program for a local business that will allow its users to easily keep track of their job service tickets. At one point, I had the program working with no errors. However, being that I am very new to programming and had typed a lot of messy code, I went back and decided to clean some of it up, as well as added better functionality. In doing so, I caused an error to occur, and I can't seem to fix it.
I have tried multiple ways to solve this issue, but all I have been able to determine is that if I remove a small block of code, it will essentially run as it should.
Here are the two definitions:
def gui_elements_remove(self, elements):
for element in elements:
element.destroy()
def Load(self):
self.gui_elements_remove(self.gui_elements)
var = self.FirstTree.focus()
treevar = self.FirstTree.item(var)
JobDF = pd.DataFrame(treevar, index = [0])
self.JobID = JobDF.iloc[0]['values']
cursor1 = self.cursor.execute("SELECT * from 'Service Tickets' WHERE JobID = ?", (self.JobID))
SP = cursor1.fetchall()
df = pd.DataFrame(SP, columns = [
'ServiceTicketsID',
'JobID',
'Ticket Number',
'Reason for Visit',
'Warranty/VPO',
'Date Ticket Received',
'Date of Service',
'Service Person'
])
columns = [
'ServiceTicketsID',
'Ticket Number',
'Reason for Visit',
'Warranty/VPO',
'Date Ticket Received',
'Date of Service',
'Service Person'
]
LoadFrame = Frame(self.root)
LoadFrame.pack(fill = BOTH, expand = True, padx = 50)
scroll = Scrollbar(LoadFrame, orient = VERTICAL)
SecondTree = ttk.Treeview(LoadFrame, yscrollcommand = scroll.set, columns = columns)
SecondTree['show'] = 'headings'
SecondTree.heading('#1', text = 'ServiceTicketsID')
SecondTree.heading('#2', text = 'Ticket Number')
SecondTree.heading('#3', text = 'Reason for Visit')
SecondTree.heading('#4', text = 'Warranty/VPO')
SecondTree.heading('#5', text = 'Date Ticket Received')
SecondTree.heading('#6', text = 'Date of Service')
SecondTree.heading('#7', text = 'Service Person')
SecondTree.column('#1', width = 0, stretch = NO)
SecondTree.column('#2', width = 75, stretch = YES, anchor = "n")
SecondTree.column('#3', width = 75, stretch = YES, anchor = "n")
SecondTree.column('#4', width = 75, stretch = YES, anchor = "n")
SecondTree.column('#5', width = 100, stretch = YES, anchor = "n")
SecondTree.column('#6', width = 100, stretch = YES, anchor = "n")
SecondTree.column('#7', stretch = YES, anchor = "n")
scroll.config(command = SecondTree.yview)
scroll.pack(side = RIGHT, fill = Y)
SecondTree.pack(fill = BOTH, expand = TRUE)
Maintree = df [[
'ServiceTicketsID',
'Ticket Number',
'Reason for Visit',
'Warranty/VPO',
'Date Ticket Received',
'Date of Service',
'Service Person'
]]
Maintree_rows = Maintree.to_numpy().tolist()
for row in Maintree_rows:
SecondTree.insert("", 0, values = row)
for col in columns:
SecondTree.heading(col, text=col, command=lambda _col=col: \
self.Treeview_sort_column(SecondTree, _col, False))
b1 = Button(LoadFrame, text = "Add")
b2 = Button(LoadFrame, text = "Update")
b3 = Button(LoadFrame, text = "Cancel")
b1.configure(command = lambda: self.Load_Add())
b2.configure(command = lambda: self.Load_Update())
#b3.configure(command = lambda: self.Cancel_Button(LoadFile, self.MainWindow, self.root))
b1.pack(side = LEFT, padx = 5, pady = 50)
b2.pack(side = LEFT, padx = 5, pady = 50)
b3.pack(side = LEFT, padx = 5, pady = 50)
There is more code but what I've provided should be more than enough, hopefully. The first method is called to clear out the frame from the previous method being run. The remainder is supposed to take the user's selection from the previous TreeView. However, upon running this code, I am given the error
_tkinter.TclError: invalid command name ".!frame3.!treeview"
If I comment out this block of code:
var = self.FirstTree.focus()
treevar = self.FirstTree.item(var)
JobDF = pd.DataFrame(treevar, index = [0])
self.JobID = JobDF.iloc[0]['values']
cursor1 = self.cursor.execute("SELECT * from 'Service Tickets' WHERE JobID = ?", (self.JobID))
SP = cursor1.fetchall()
df = pd.DataFrame(SP, columns = [
'ServiceTicketsID',
'JobID',
'Ticket Number',
'Reason for Visit',
'Warranty/VPO',
'Date Ticket Received',
'Date of Service',
'Service Person'
])
As well as the remaining code affiliated with it, the program runs correctly. The frame is cleared out and the new TreeView is brought in, as well as its headers.
As I said, I am brand new to programming and as such am new to using StackOverflow. With that said, I apologize if I've not provided enough information, posted incorrectly, etc. I also apologize in advance for any sloppiness you may find in the code lol.
I appreciate all input.
JobDF = pd.DataFrame(treevar, index = [0])
and
cursor1 = self.cursor.execute("SELECT * from 'Service Tickets' WHERE JobID = ?", (self.JobID))
were the culprits.
It should have been
JobDF = pd.DataFrame(treevar) #removed the index argument
and
cursor1 = self.cursor.execute("SELECT * from 'Service Tickets' WHERE JobID = ?", (self.JobID,)) #added a comma (self.JobID,))

Plotly Geoscatter with Aggregation: show aggregation in hover Info

I have been trying to create a Geoscatter Plot with Plotly where the marker size should indicate the number of customers (row items) in one city (zip_city). I based my code on two templates from the Plotly documentation: United States Bubble Map and the aggregation part Mapping with Aggregates.
I managed to put together a code that does what I want, except for one drawback: when I hover over a bubble, I would like to see the name of the city plus number of customers (the result from the aggregation), so something like Aguadilla: 2. Can you help me on how to do this?
Here is my code (as a beginner with plotly, I am also open to code improvements):
import plotly.offline as pyo
import pandas as pd
df = pd.DataFrame.from_dict({'Customer': [111, 222, 555, 666],
'zip_city': ['Aguadilla', 'Aguadilla', 'Arecibo', 'Wrangell'],
'zip_latitude':[18.498987, 18.498987, 18.449732,56.409507],
'zip_longitude':[-67.13699,-67.13699,-66.69879,-132.33822]})
data = [dict(
type = 'scattergeo',
locationmode = 'USA-states',
lon = df['zip_longitude'],
lat = df['zip_latitude'],
text = df['Customer'],
marker = dict(
size = df['Customer'],
line = dict(width=0.5, color='rgb(40,40,40)'),
sizemode = 'area'
),
transforms = [dict(
type = 'aggregate',
groups = df['zip_city'],
aggregations = [dict(target = df['Customer'], func = 'count', enabled = True)]
)]
)]
layout = dict(title = 'Customers per US City')
fig = dict( data=data, layout=layout )
pyo.plot( fig, validate=False)
Update:
Can I access the result of the transforms argument directly in the data argument to show the number of customers per city?
You can create a list, that will contains what you want and then set text=list in data. Also do not forget specify hoverinfo='text'.
I am updated your code, so try this:
import pandas as pd
import plotly.offline as pyo
df = pd.DataFrame.from_dict({'Customer': [111, 222, 555, 666],
'zip_city': ['Aguadilla', 'Aguadilla', 'Arecibo', 'Wrangell'],
'zip_latitude':[18.498987, 18.498987, 18.449732,56.409507],
'zip_longitude':[-67.13699,-67.13699,-66.69879,-132.33822]})
customer = df['Customer'].tolist()
zipcity = df['zip_city'].tolist()
list = []
for i in range(len(customer)):
k = str(zipcity[i]) + ':' + str(customer[i])
list.append(k)
data = [dict(
type = 'scattergeo',
locationmode = 'USA-states',
lon = df['zip_longitude'],
lat = df['zip_latitude'],
text = list,
hoverinfo = 'text',
marker = dict(
size = df['Customer'],
line = dict(width=0.5, color='rgb(40,40,40)'),
sizemode = 'area'
),
transforms = [dict(
type = 'aggregate',
groups = df['zip_city'],
aggregations = [dict(target = df['Customer'], func = 'count', enabled = True)]
)]
)]
layout = dict(title = 'Customers per US City')
fig = dict(data=data, layout=layout)
pyo.plot(fig, validate=False)

Categories

Resources