LoginSignup
11
8

More than 1 year has passed since last update.

Streamlitでのページ遷移とポップアップボタンの実装

Last updated at Posted at 2022-01-07

1次、2次、、と深く潜るタイプのページ遷移のテクニックが見つからなかったので自分なりに実装してみました。

ページ遷移

Streamlitを使っていると、

Untitled Diagram.jpg

この一連のページ遷移を実装したい場面が出てきます。
これを実現します。

ポップアップボタン

See the Pen Untitled by py guda (@gudapy) on CodePen.

一度切りの押しボタンです。一定時間後に消えるポップアップを表示したり、処理のトリガーとなります。ポップアップメッセージ自体はst.spinnerを使うと出来ます。しかしページ遷移との組み合わせにすると、ページ遷移後にボタン操作などを行った場合、『ページの状態がクリアされてしまう』『ポップアップメッセージが消えない』、といった問題がいくつか出てきます。ページ遷移後の状態を保持しつつポップアップ(ボタン)を実装する、これを実現します。

肝となるStreamlit API

st.session_state()
st.empty()
st.spinner()

ノートブック

Streamlitでのページ遷移とポップアップボタン

動作

ページ移動からの、ページ遷移+ポップアップの一連の動作です。
逐次処理順に番号[n]を振りました。

GIF動画です

output-palette.gif

使い方

メニューバーから、

ランタイム > すべてのセルを実行

そもそもGoogle Colaboratoryの使い方は、という方はこちら。

ソース

ベースはこれです。

app.py

import streamlit as st
import time

pop()pop_btn()timeが必要です。

# config
st.set_page_config(
    page_title="Streamlitでのページ遷移とポップアップボタン",
    layout="wide",
    initial_sidebar_state="expanded"
)

ページのタイトルや幅を設定できます。

st.set_page_config
https://docs.streamlit.io/library/api-reference/utilities/st.set_page_config

# init
def init():
    if "init" not in st.session_state:
        st.session_state.init=True
        reset_session()
        count()
        return True
    else:
        return False

# st.sidebar.selectboxの切り替わりを感知
def tab_session():
    if not st.session_state.now_tab==st.session_state.tab:
        reset_session()
    st.session_state.now_tab=st.session_state.tab

def layer_session(layer=0):
    st.session_state.layer=layer

def reset_session():
    st.session_state.now_tab=None
    layer_session()

主にページ遷移処理部分を管理しています。
表示ページの途中でst.sidebar.selectboxが切り替わった場合にlayerをリセットして対応しています。

st.session_state
https://docs.streamlit.io/library/api-reference/session-state

# extends
def ck():
    if "ck" not in st.session_state:
        st.session_state.ck=-1
    st.session_state.ck+=1
    return st.session_state.ck

def count():
    if "count" not in st.session_state:
        st.session_state.count=0
    st.session_state.count+=1

webアプリとしての処理部分です。例えばここにAPIを叩く処理など好きな処理を追加していきます。

# UI components
def deco_horizontal(func):
    def wrapper(*args, **kwargs):
        st.write("---")
        func(*args, **kwargs)
        st.write("---")
    return wrapper

@deco_horizontal
def back_btn():
    st.button(f"戻る。[{ck()}]",on_click=reset_session)

def btn(label="ボタン",key=None,onclick=None,done=None):
    if st.button(label,key=key,on_click=onclick) and done:
        done()

def pop_btn(label="pop",key=None, layer=0, onclick=lambda:None, done=None, description=None):
    placeholder=st.empty()
    with placeholder.container():
        if description:
            st.write(description)
        res=st.button(label,key=key,on_click=lambda:[placeholder.empty(),layer_session(layer),onclick()])
    if res:
        if done:
            with placeholder:
                done()
                placeholder.empty()

def pop(msg, done=None, interval=1):
    with st.spinner(msg):
        time.sleep(interval)
    if done:
        done()

ポップアップボタンと必要最低限のUI部品です。
ポップアップ後の処理とポップアップボタンそのものが残らないようにplaceholder.empty()によってクリアしています。
doneには一度表示させて消したい処理を登録します。
onclickには表示に関わりのない処理を登録します。
onclickst.writeなどを登録すると(逐次処理的に)ページ上部に表示されてしまいます。

st.empty
https://docs.streamlit.io/library/api-reference/layout/st.empty

# contents
def index():
    st.write("ここは **Indexページ** です。")

def sample_content(i):
    st.write(f"ここは **ページ {i}** です。[{ck()}]")
    pop_btn(
        label=f"POPボタン",
        layer=2,
        onclick=lambda:[count(),print(f"ck[{ck()}]")],
        done=lambda:[
            pop(f"POPボタンが押されました[{ck()}]"),
            st.success(f"成功![{ck()}]"),
            time.sleep(1)
        ]
    )

ページのコンテンツ部分です。
ボタンが押された直後にカウントするようonclickにcount()を登録しました。
ボタンが押された後の表示処理としてdoneに逐次処理を登録しています。
st.columnsst.sidebarによって出力場所を変えることもできます。

st.columns
https://docs.streamlit.io/library/api-reference/layout/st.columns
st.sidebar
https://docs.streamlit.io/library/api-reference/layout/st.sidebar

# body
init()
st.session_state.ck=0

st.session_state.tab = st.sidebar.selectbox("選択してください。", ["Index","List"])
tab_session()# TAB切り替えの管理
# delay
time.sleep(0.1)
#
_tab=st.session_state.tab
_layer=st.session_state.layer
if _tab=="Index":
    if _layer==0 or _layer==1:
        layer_session(1)
        index()
elif _tab=="List":
    if _layer==0 or _layer==1:
        layer_session(1)
        sample_content(1)
    elif _layer==2:
        st.info(f"POPによるページ遷移をしました。[{ck()}]")
        sample_content(1)
        back_btn()

各コンテンツにはst.sidebarでアクセスします。
st.sidebarでなくとも、条件分岐で表示させることができれば、どの方法でも良いと思います。
今どこのページにいるかはst.sesion_state.tabst.session_state.layerで判別しています。
st.session_state.tabは各コンテンツ。
st.session_state.layerは深層で分岐することによってコンテンツ内遷移をしています。

st.sidebar
https://docs.streamlit.io/library/api-reference/layout/st.sidebar

おわりに

汎用的に使えて動作もしますが、やり方はこれであっているのかはわかりません。
他にもっと良い方法があるのかもしれません。

参考

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8