|
|
# Streamlit Patterns
|
|
|
|
|
|
## Session State Initialization
|
|
|
|
|
|
Always initialize session state at the start of the main function:
|
|
|
|
|
|
```python
|
|
|
# app.py
|
|
|
import streamlit as st
|
|
|
|
|
|
def main():
|
|
|
# Initialize all session state variables
|
|
|
if "session_id" not in st.session_state:
|
|
|
st.session_state.session_id = None
|
|
|
if "current_motion_index" not in st.session_state:
|
|
|
st.session_state.current_motion_index = 0
|
|
|
if "motions" not in st.session_state:
|
|
|
st.session_state.motions = []
|
|
|
if "show_results" not in st.session_state:
|
|
|
st.session_state.show_results = False
|
|
|
|
|
|
# Rest of app...
|
|
|
```
|
|
|
|
|
|
## Page Configuration
|
|
|
|
|
|
Set page config at the top of each page file:
|
|
|
|
|
|
```python
|
|
|
# pages/1_Stemwijzer.py
|
|
|
import streamlit as st
|
|
|
|
|
|
st.set_page_config(
|
|
|
page_title="Stemwijzer",
|
|
|
page_icon="🗳️",
|
|
|
layout="centered",
|
|
|
)
|
|
|
|
|
|
from explorer import build_mp_quiz_tab
|
|
|
build_mp_quiz_tab("data/motions.db")
|
|
|
```
|
|
|
|
|
|
## Thin Page Wrapper Pattern
|
|
|
|
|
|
Pages delegate to shared functions in main modules:
|
|
|
|
|
|
```python
|
|
|
# pages/2_Explorer.py
|
|
|
import streamlit as st
|
|
|
|
|
|
st.set_page_config(
|
|
|
page_title="Explorer",
|
|
|
page_icon="🔭",
|
|
|
layout="wide",
|
|
|
)
|
|
|
|
|
|
from explorer import build_explorer_tab
|
|
|
build_explorer_tab()
|
|
|
```
|
|
|
|
|
|
```python
|
|
|
# explorer.py
|
|
|
def build_explorer_tab():
|
|
|
st.header("🔭 Politiek Explorer")
|
|
|
|
|
|
tab1, tab2, tab3 = st.tabs([
|
|
|
"Compass",
|
|
|
"Trajectories",
|
|
|
"Zoeken"
|
|
|
])
|
|
|
|
|
|
with tab1:
|
|
|
render_compass()
|
|
|
with tab2:
|
|
|
render_trajectories()
|
|
|
with tab3:
|
|
|
render_search()
|
|
|
```
|
|
|
|
|
|
## Sidebar Pattern
|
|
|
|
|
|
Use sidebar for configuration and navigation:
|
|
|
|
|
|
```python
|
|
|
# app.py
|
|
|
def main():
|
|
|
with st.sidebar:
|
|
|
st.header("Instellingen")
|
|
|
|
|
|
motion_count = st.slider(
|
|
|
"Aantal moties",
|
|
|
min_value=5,
|
|
|
max_value=25,
|
|
|
value=10,
|
|
|
)
|
|
|
|
|
|
policy_area = st.selectbox("Beleidsgebied", config.POLICY_AREAS)
|
|
|
|
|
|
if st.button("Start Nieuwe Sessie"):
|
|
|
start_new_session(motion_count, policy_area)
|
|
|
```
|
|
|
|
|
|
## Callback Pattern for State Updates
|
|
|
|
|
|
Use callbacks to handle user interactions:
|
|
|
|
|
|
```python
|
|
|
def on_motion_vote(motion_id: int, vote: str):
|
|
|
"""Callback when user votes on a motion."""
|
|
|
st.session_state.user_votes[motion_id] = vote
|
|
|
|
|
|
# Move to next motion
|
|
|
if st.session_state.current_motion_index < len(st.session_state.motions) - 1:
|
|
|
st.session_state.current_motion_index += 1
|
|
|
else:
|
|
|
st.session_state.show_results = True
|
|
|
|
|
|
st.rerun()
|
|
|
|
|
|
# In UI
|
|
|
col1, col2, col3 = st.columns(3)
|
|
|
with col1:
|
|
|
st.button("👍 Voor", on_click=on_motion_vote, args=(motion_id, "Voor"))
|
|
|
with col2:
|
|
|
st.button("👎 Tegen", on_click=on_motion_vote, args=(motion_id, "Tegen"))
|
|
|
with col3:
|
|
|
st.button("❓ Onthouden", on_click=on_motion_vote, args=(motion_id, "Onthouden"))
|
|
|
```
|
|
|
|
|
|
## Container Pattern for Dynamic Content
|
|
|
|
|
|
Use containers for dynamic rendering:
|
|
|
|
|
|
```python
|
|
|
def show_motion_interface():
|
|
|
if not st.session_state.motions:
|
|
|
st.warning("Geen moties geladen")
|
|
|
return
|
|
|
|
|
|
current_idx = st.session_state.current_motion_index
|
|
|
motion = st.session_state.motions[current_idx]
|
|
|
|
|
|
with st.container():
|
|
|
st.subheader(f"Motie {current_idx + 1} van {len(st.session_state.motions)}")
|
|
|
st.markdown(f"**{motion['title']}**")
|
|
|
st.caption(f"📅 {motion['date']} | 🏷️ {motion['policy_area']}")
|
|
|
|
|
|
if motion.get("layman_explanation"):
|
|
|
st.info(motion["layman_explanation"])
|
|
|
|
|
|
# Voting buttons...
|
|
|
```
|
|
|
|
|
|
## Expander Pattern for Details
|
|
|
|
|
|
Use expanders for collapsible content:
|
|
|
|
|
|
```python
|
|
|
with st.expander("Meer details"):
|
|
|
st.markdown(f"**Beschrijving:** {motion.get('description', 'N/A')}")
|
|
|
|
|
|
if motion.get("voting_results"):
|
|
|
results = json.loads(motion["voting_results"])
|
|
|
st.json(results)
|
|
|
```
|
|
|
|
|
|
## Form Pattern for Batch Updates
|
|
|
|
|
|
Use forms for multiple related inputs:
|
|
|
|
|
|
```python
|
|
|
with st.form("session_settings"):
|
|
|
st.subheader("Sessie Instellingen")
|
|
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
with col1:
|
|
|
count = st.number_input("Aantal moties", min_value=5, max_value=25)
|
|
|
with col2:
|
|
|
area = st.selectbox("Beleidsgebied", config.POLICY_AREAS)
|
|
|
|
|
|
submitted = st.form_submit_button("Start Sessie")
|
|
|
if submitted:
|
|
|
start_session(count, area)
|
|
|
```
|
|
|
|
|
|
## Caching Pattern
|
|
|
|
|
|
Cache expensive computations:
|
|
|
|
|
|
```python
|
|
|
@st.cache_data(ttl=3600) # Cache for 1 hour
|
|
|
def load_party_positions(window_id: str) -> Dict:
|
|
|
"""Load party positions from database."""
|
|
|
return db.get_party_positions(window_id)
|
|
|
|
|
|
@st.cache_resource
|
|
|
def init_database():
|
|
|
"""Initialize database connection."""
|
|
|
return MotionDatabase(config.DATABASE_PATH)
|
|
|
```
|
|
|
|
|
|
## Home Page Pattern
|
|
|
|
|
|
Landing page with navigation:
|
|
|
|
|
|
```python
|
|
|
# Home.py
|
|
|
import streamlit as st
|
|
|
|
|
|
st.set_page_config(
|
|
|
page_title="Motief: de stematlas",
|
|
|
page_icon="🗺️",
|
|
|
layout="centered",
|
|
|
)
|
|
|
|
|
|
def main():
|
|
|
st.title("🗺️ Motief: de stematlas")
|
|
|
st.markdown("**Motief** brengt de Nederlandse Tweede Kamer in kaart...")
|
|
|
|
|
|
col1, col2 = st.columns(2)
|
|
|
with col1:
|
|
|
st.page_link("pages/1_Stemwijzer.py", label="Open Stemwijzer", icon="🗳️")
|
|
|
with col2:
|
|
|
st.page_link("pages/2_Explorer.py", label="Open Explorer", icon="🔭")
|
|
|
```
|
|
|
|