Summary
I am using a rabbitmq message broker to ‘stream’ information into a streamlit dashboard. The dashboard has multiple tabs, within each tab are multiple columns, each column contains 4 Charts which are the target destination for the incoming data. The idea is to have the data coming out of rabbitmq update its target chart. Each data packet corresponds to a tab and contains all of the required data to update each chart element within a tab. For reproducability I have substituted the Consumer from rabbit for a random number generator which updates dataframes just like they will in the real thing.
Code snippet:
# DUMMY CODE - NOT RUNNABLE
import streamlit as st
import json
import random as rnd
from datetime import datetime
import time
from queue import Queue
import pandas as pd
import altair as alt
import threading
def generate_random(names: list[str],q: Queue):
names = ['Foo','Bar','Baz','Cat','Dog','Mouse']
ds = dict((n,{'name':n,'df1':1,'df2':1,'df3':1,'df4':1,'timestamp':None}) for n in names)
while True:
cur = rnd.choice(names)
ds[cur]['df1'] = ds[cur]['df1'] * rnd.uniform(0.9,1.1)
ds[cur]['df2'] = ds[cur]['df2'] * rnd.uniform(0.9,1.1)
ds[cur]['df3'] = ds[cur]['df3'] * rnd.uniform(0.9,1.1)
ds[cur]['df4'] = ds[cur]['df4'] * rnd.uniform(0.9,1.1)
ds[cur]['timestamp'] = datetime.now()
time.sleep((1/len(names)) * rnd.uniform(0.9,1.1))
q.put(ds[cur])
class DataStore:
def __init__(self,name: str) -> None:
self.name = name
self.df1 = pd.DataFrame(columns=['df1'])
self.df2 = pd.DataFrame(columns=['df2'])
self.df3 = pd.DataFrame(columns=['df3'])
self.df4 = pd.DataFrame(columns=['df4'])
def update(self, d: dict) -> None:
self.df1['df1'].loc[d['timestamp']] = d['df1']
self.df2['df2'].loc[d['timestamp']] = d['df2']
self.df3['df3'].loc[d['timestamp']] = d['df3']
self.df4['df4'].loc[d['timestamp']] = d['df4']
if __name__=='__main__':
print('starting')
names = ['Foo','Bar','Baz','Cat','Dog','Mouse']
q = Queue()
# generate_random(names=names,q=q)
t = threading.Thread(target=generate_random,args=(names,q))
t.start()
print('generating')
st.title('Dummy Live Streamer')
# list of tuples of (DataStore,Tab,(col1,col2))
tabcombs = [(DataStore(name),t,st.columns(2)) for name,t in zip(names,st.tabs(names))]
# Do I make the columns for each tab here?
# Where does st.empty() go to - to stop repeated writes?
print('got to while loop')
# breakpoint()
while True:
data = q.get()
print(data)
try:
data = json.loads(data)
except:
pass
cur_data,cur_tab,(col1,col2) = list(filter(lambda x: x[0].name == data['name'],tabcombs))[0]
cur_data.update(data)
with cur_tab:
with col1:
c1 = alt.Chart(cur_data.df1.reset_index()).mark_line().encode(x='index',y='df1').properties(title='df1',width=400,height=200)
c2 = alt.Chart(cur_data.df2.reset_index()).mark_line().encode(x='index',y='df2').properties(title='df2',width=400,height=200)
st.write(c1)
st.write(c2)
with col2:
c3 = alt.Chart(cur_data.df3.reset_index()).mark_line().encode(x='index',y='df3').properties(title='df3',width=400,height=200)
c4 = alt.Chart(cur_data.df4.reset_index()).mark_line().encode(x='index',y='df4').properties(title='df4',width=400,height=200)
st.write(c3)
st.write(c4)
# This is where I run out of ideas.
Hoping the random number generator works! My wishlist is:
- That there are 2 Graphs per column per tab.
- That the graphs update inplace.
- That the kind person responding helps me understand why they have defined the streamlit elements where they end up defining them. I may need to tweak the structure moving forward and don’t want to have to duplicate the question!
- I’m 99% convinced that somewhere in each tab there has to be an
st.empty()
to avoid writing the new graph beneath the old one. Looking like 1 datapacket per tab per second so that gets out of hand quite quickly.
Streamlit version = Version: 1.22.0
Thank you for your help in advance.