📊

Streamlitで作ったダッシュボードを自動でスライドショーさせる

2024/01/24に公開

今回作ったもの

Streamlitで作ったダッシュボードを自動でスライドショーできるか試してみました。
↓が完成したものです。

※スライドバーやチェックボックスを操作するたびに再描画され、乱数で作っているグラフが変化しているのはご愛嬌です。

一応イメージしていたものはできました。

https://github.com/0msys/streamlit-slide-show-test

作成した背景

複数のダッシュボードを自動でスライドショーで回しながら常設ディスプレイで表示しておきたいことがあり、今まではStreamlitではできなかったのでGrafanaを使ってやっていたのですが、Grafanaはそれなりの知識や設定が必要ですし、DBからGrafana表示用にPythonで加工してDBに戻して・・・みたいな面倒なことをやっていたので、なかなか辛くなってきていました。

Streamlitが1.30.0に更新されてst.switch_pageが追加され、マルチページアプリのページ遷移がプログラム上から可能になったので、やりたいことができるか試してみました。

実装手順

Streamlit開発環境の準備

まずこの前作ったテンプレートを使って、Streamlitのプロジェクトを作ります。

https://zenn.dev/0msys/articles/3524948d15c8d5

GithubにてUse this templateを押してリポジトリを作って、
ローカルにクローンして必要な個所のtemplateslide-showに置き換えたら開発の準備は完了です。
(面倒なのでコメント含めて一括置き換えしてます)

.devcontainer/devcontainer.json
<  "name": "template-container",   // "template"を任意の名前にする
<  "service": "template-dev",      // "template"をcompose-dev.ymlと同じにする
---
>  "name": "slide-show-container",   // "slide-show"を任意の名前にする
>  "service": "slide-show-dev",      // "slide-show"をcompose-dev.ymlと同じにする
.devcontainer/compose-dev.yml
<  template-dev:   # "template"を任意の名称に変更
<    container_name: template-dev    # "template"を任意の名称に変更
---
>  slide-show-dev:   # "slide-show"を任意の名称に変更
>    container_name: slide-show-dev    # "slide-show"を任意の名称に変更
compose.yml
<  template:   # "template"を任意の名前に変更
<    container_name: template    # "template"を任意の名前に変更
---
>  slide-show:   # "slide-show"を任意の名前に変更
>    container_name: slide-show    # "slide-show"を任意の名前に変更

準備が整ったら、VSCodeでdevcontainerを起動します。

ダッシュボードの作成

適当なデータでグラフのあるページを3つほど作ります。

こだわっても仕方ないので、Streamlitのドキュメントにあるサンプルコードをコピペします。

src/pages/page1.py
import streamlit as st
import pandas as pd
import numpy as np

st.title("ダッシュボード1")

chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])

st.area_chart(chart_data)
src/pages/page2.py
import streamlit as st
import pandas as pd
import numpy as np

st.title("ダッシュボード2")

chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])

st.bar_chart(chart_data)
src/pages/page3.py
import streamlit as st
import pandas as pd
import numpy as np

st.title("ダッシュボード3")

chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])

st.line_chart(chart_data)

ページ遷移の実装

st.switch_pageを使って、ページ遷移を実装します。

ドキュメントをみたところ、単純にst.switch_pageに遷移先のページを渡せばいいようです。

各ページに3秒で次のページに遷移するように実装します。

src/pages/page1.py
+ import time
+ 
+ time.sleep(3)
+ st.switch_page("pages/page2.py")
src/pages/page2.py
+ import time
+ 
+ time.sleep(3)
+ st.switch_page("pages/page3.py")
src/pages/page3.py
+ import time
+ 
+ time.sleep(3)
+ st.switch_page("pages/page1.py")

↓すんなりと動きました🎉

やりたいことが実現できることが分かったので、もう少し使いやすい形にしてみます。

最終的な実装

slide_show.pyを作成して、そこにページ遷移の処理をまとめました。

スライドバーでスライドの表示時間を変更できるようにし、チェックボックスでスライドショーの開始/停止を切り替えられるようにしました。
おまけとして、スライドショー中は進捗バーを表示してますが、これは冗長なので必要なければ削除してください。

前のページの設定を引き継ぐために、st.session_stateを使っていますが、
スライドバーとチェックボックスの初期値の渡し方によってはうまく動作しないパターンがあり、若干複雑になってしまいました。

src/slide_show.py
import streamlit as st
import time
import datetime


def slide_show(page):
    # 前のページで設定したスライドショーの設定が無ければ、初期値を設定
    if "slide_enable_previous" not in st.session_state:
        st.session_state.slide_enable_previous = False

    if "slide_time_previous" not in st.session_state:
        st.session_state.slide_time_previous = 20

    # スライダーをサイドバーに表示し、スライドの表示時間を変更できるようにする
    st.sidebar.slider(
        label="スライドの表示時間(秒)",
        min_value=5,
        max_value=60,
        value=st.session_state.slide_time_previous,
        step=5,
        key="slide_time",
    )

    # チェックボックスをサイドバーに表示し、スライドショーの開始/停止を切り替えられるようにする
    st.sidebar.checkbox(
        label="スライドショーを開始する",
        value=st.session_state.slide_enable_previous,
        key="slide_enable",
    )

    # スライドショーが有効な場合は指定された時間だけページを表示し、進捗バーを更新
    if st.session_state.slide_enable:
        endtime = datetime.datetime.now() + datetime.timedelta(
            seconds=int(st.session_state.slide_time)
        )
        progress_bar = st.progress(
            0, f"スライドショー中... 0 / {st.session_state.slide_time} 秒"
        )
        while datetime.datetime.now() < endtime:
            time.sleep(1)
            progress_time = int(
                st.session_state.slide_time
                - (endtime - datetime.datetime.now()).total_seconds()
            )
            progress_value = progress_time / st.session_state.slide_time
            progress_bar.progress(
                progress_value,
                text=f"スライドショー中... {progress_time} / {st.session_state.slide_time} 秒",
            )

        # 設定時間が経過したら、指定されたページに切り替える
        # 次ページでスライドショーを継続するために、スライドショーの設定を保存
        st.session_state.slide_enable_previous = True
        st.session_state.slide_time_previous = st.session_state.slide_time
        st.switch_page(page)
    else:
        # スライドショーを止めて別ページに移動した場合に、スライドショーが勝手に再開しないようにする
        st.session_state.slide_enable_previous = False

各ページは、slide_showをインポートして、slide_showに遷移先のページを渡すだけになりました。
気を付けるのは、slide_showの呼び出しはページの末尾に置くことです。

src/pages/page1.py
- import time
- 
- time.sleep(3)
- st.switch_page("pages/page2.py")
---
+ from slide_show import slide_show
+
+ slide_show("pages/page2.py")
src/pages/page2.py
- import time
-
- time.sleep(3)
- st.switch_page("pages/page3.py")
---
+ from slide_show import slide_show
+
+ slide_show("pages/page3.py")
src/pages/page3.py
- import time
-
- time.sleep(3)
- st.switch_page("pages/page1.py")
---
+ from slide_show import slide_show
+
+ slide_show("pages/page1.py")

完成

https://github.com/0msys/streamlit-slide-show-test

まとめ

今回確認した通り、Streamlitのダッシュボードを自動でスライドショーできるようになりました。

GrafanaはGrafanaでいいところが有るので完全置き換えができるかはわかりませんが、
選択肢が増えたのは嬉しいですね。

Discussion