I am designing an small annotation interface which will present users with a series of examples. An example will be presented to a user, they will make a mutually exclusive selection using a radio button, then more information will be presented in a second button where they can make a new selection with the same options; after submitting this, they move to the next example. I have the following code:
#!/usr/bin/env python3
import streamlit as st
columns = ["question", "answerA", "answerB", "answerC", "explanationA", "explanationB", "explanationC", "answer"]
with open('fake_data.tsv') as f:
examples = [dict(zip(columns, line.strip().split("\t"))) for line in f]
def show_example(examples, i, answers):
example = examples[i]
st.header("context: TODO, wire this up")
answer_before = st.radio(example["question"], [example["answerA"], example["answerB"], example["answerC"], "multiple options"], key=f"q1-{i}")
if st.button("submit", key=f"b1-{i}"):
answer_after = st.radio(example["question"], [
": ".join((example["answerA"], example["explanationA"])),
": ".join((example["answerB"], example["explanationB"])),
": ".join((example["answerC"], example["explanationC"])),
"multiple options"
], key=f"q2-{i}")
if i + 1 == len(examples):
print("Saving answers")
else:
if st.button("next question", key=f"b2-{i}"):
answers.append((answer_before, answer_after))
return show_example(examples, i+1, answers)
show_example(examples, 0, [])
which when run with a sample data file:
but when a different selection is made in (3), the second radio button disappears completely.
My data file is here:
fake_data.tsv
:question0 answerA0 answerB0 answerC0 explanationA0 explanationB0 explanationC0 acceptedAnswer0
question1 answerA1 answerB1 answerC1 explanationA1 explanationB1 explanationC1 acceptedAnswer1
question2 answerA2 answerB2 answerC2 explanationA2 explanationB2 explanationC2 acceptedAnswer2
question3 answerA3 answerB3 answerC3 explanationA3 explanationB3 explanationC3 acceptedAnswer3
How can I achieve my desired interaction? I'm using streamlit 1.22.0.
You have nested buttons. A button will return True only on the page load resulting from its click, then go back to False. If you nest something inside a button, it will disappear as soon as the user makes their next move. Therefore, anything conditioned directly on a button should be transient--meant to execute once and go away.
When you need something to display conditioned on a button having been pressed any time in the past, you would need the button to toggle some flag in session state, then use that for your conditional. (See point one in this Streamlit blog post.)
For your example, here's some edits:
You basically have two stages for each question (before and after). I created a flag in session state using the key after
. It is initially False
, becomes True
when someone submits a "before" answer, then goes back to False
when someone submits their "after" answer and proceeds to the next question. The buttons affect this value in session state via callbacks.
As a little extra, I set the first button and radio widget to disable when it's clicked so the user can't go back and change their "before" answer.
I left the keys in place, but they aren't needed as it currently runs with each question just being displayed by itself. If you further change it so the questions are displayed simultaneously, then you'd need keys to keep your submit keys distinct, for instance.
import streamlit as st
import pandas as pd
columns = ["question", "answerA", "answerB", "answerC", "explanationA", "explanationB", "explanationC", "answer"]
#Changed to inline executable example
examples = pd.DataFrame(
[[f'q{i}',f'A{i}',f'B{i}',f'C{i}',f'exA{i}',f'exB{i}',f'exC{i}',f'a{i}',] for i in range(10)],
columns=columns
)
# Initialize Values in Session State
if 'answers' not in st.session_state:
st.session_state.after = False # To track whether "before" vs "after"
st.session_state.done = False # To track the last submission when there is no next
st.session_state.i = 0 # To track which question the user is on
st.session_state.answers = [] # To save the answers
def after():
'''Change 'after' key in session state to True
'''
st.session_state.after = True
def next(answers, answer_before, answer_after):
'''Change 'after' key back to false, increment question tracker, save answers
'''
st.session_state.after = False
st.session_state.i += 1
answers.append((answer_before, answer_after))
def done(answers, answer_before, answer_after):
'''Change 'done' key to True and save answers to final question
'''
st.session_state.done = True
answers.append((answer_before, answer_after))
def show_example(examples, i, answers):
#Changed to match example
#example = examples[i]
example = examples.iloc[i]
st.header("context: TODO, wire this up")
answer_before = st.radio(
example["question"],
[example["answerA"], example["answerB"], example["answerC"], "multiple options"],
key=f"q1-{i}",
disabled=st.session_state.after)
st.button("submit", key=f"b1-{i}", disabled=st.session_state.after, on_click=after)
if st.session_state.after:
answer_after = st.radio(example["question"], [
": ".join((example["answerA"], example["explanationA"])),
": ".join((example["answerB"], example["explanationB"])),
": ".join((example["answerC"], example["explanationC"])),
"multiple options"
], key=f"q2-{i}", disabled=st.session_state.done)
if i + 1 == len(examples):
st.button('Done', on_click=done, disabled=st.session_state.done, args=[answers,answer_before,answer_after])
if st.session_state.done:
st.write(st.session_state.answers)
else:
st.button("next question", key=f"b2-{i}", on_click=next, args=[answers,answer_before,answer_after])
show_example(examples, st.session_state.i, st.session_state.answers)