Streamlit で DataFrame widget ありますが,
あんまり機能ありません.
また, 将来的に他の UI framework 使いたいときになるべく同じの使いたい...
aggrid(JavaScript ライブラリ. 通常利用は MIT ライセンスで商用利用などもできる)が使える streamlit 用のバインディングがあります!
が, なんかバインディング API がややこしい...
単にテーブルデータ表示したい + alpha くらい(bool 値は checkbox で表示とか)をしたい...
デフォルトだと sortable で filterable で煩わしいので, off にしたい...
streamlit も st_aggrid もアクティブに開発されていますので, ↓でカスタム対応しているようなものはそのうち標準機能として使えたりするかもしれません.
基本
GridOption で制御します.
st_aggrid ですと初期オプションを pandas DataFrame から自動で作ってくれる機能があります.
data = JSON のデータ
df = pandas.DataFrame(data)
gd = GridOptionsBuilder.from_dataframe(df, sortable=False, filterable=False)
あとは configure_default_column
でカラム全体か, configure_colum
でカラム指定で調整します.
チェックボックスをつける.
configure_selection(selection_mode='single', use_checkbox=True)
一見, API 名がへんてこです. レイアウトも最初の column の先頭につくのでちょっとわかりずらいです.
configure_selection
で use_checkbox
でいけます.
内部実装をみると, 最初の column に checkbox をつけるような実装になっています.
したがってこれは row をチェックボックスで選択するしない, みたいな意味合いでした.
selection_mode
を multiple にすると複数 row をチェック on/off できます.
Boolean 値を Checkbox で表示
column の boolean 値を Checkbox で表示したいときもあります.
cellRenderer
に自前で JSCode をつけて見た目を制御するしかないです...
st_aggrid では, cellRenderer に渡ったコードを JS として直接評価するようです. params.value
に cell の値が渡ってきます.
data = {
'Enabled': [True, False, False],
...
}
...
checkboxRenderer = JsCode('''
function(params) {
var value = "";
if (params.value == true) {
value = "checked"
}
return '<input type="checkbox" ' + value + '>'
}
''')
gd.configure_column('Enabled', cellRenderer=checkboxRenderer)
のように checkbox widget を返すようにしました.
とりあえず <input "checkbox">
で直接 HTML 要素を返してもスタイル適用していいかんじにやってくれます.
あと, AgGrid で allow_unsafe_jscode=True
を指定必要でした.
(Streamlit は React を利用しているので, React 要素を記述(できるのかしらん)にしたほうがより安全かもはしれません)
頑張れば URL とかも表示できるようになります.
より頑張れば React のコードも渡せるかもしれません...
check on/off の反映
ただ, これだと表示の設定しか変わらないので, ブラウザでチェック on/off しても, テーブルの値を取得してもセルの値は変わりません.
のように onclick イベントを追加して内部ステートをアップデートするようにします!
checkboxRenderer = JsCode(
"""
function(params) {
var input = document.createElement('input');
input.type="checkbox";
input.checked=params.value;
input.addEventListener('click', function (event) {
params.value=!params.value;
var colId = params.column.colId;
params.node.setDataValue(colId, params.value);
});
return input;
}
"""
)
こんな感じです.
params.value
を変更しただけでは streamlit 側には反映されないです.
params.node.setDataValue()
も呼ぶ必要があります.
(params
のデータには自身を含んだ row データが入っているので, 自身の位置を知るには column id だけわかればよい)
ただチェック入れるたびにリロードするようになるので, 大きめのテーブルデータを扱っているなどで処理低下する場合は manual update にするのも検討したほうがよいでしょう.
https://streamlit-aggrid.readthedocs.io/en/docs/AgGrid.html
(GridUpdateMode.MANUAL
を AgGrid コンストラクタで指定する)
sort off
なんか最新版では off できなくなったっぽい...?
gd.configure_default_column(filterable=False,sortable=False)
上の from_dataframe
でも指定できます(from_dataframe
の実装内部で configure_default_column
を呼んでいる)
Filter off
sort off と同様です.
ただ, 最初の column が数値データの場合は, どうしてもフィルター機能(三本線アイコン)が残ってしまうようです...(バグ?)
フィルターアイコンが煩わしい(ユーザーに操作させたくない)場合は, 無理やり column のデータを文字列にすればとりあえずは解決します...
Editable
これも configure_default_column
か configure_column
で editable
で設定できます!
gd.configure_column('X', filterable=False,sortable=False, editable=True)
Cell の値の取得
ユーザーが Cell 値をいじったりしたあとに入力値を得たい.
AgGrid()
で返ってくるオブジェクトの "data"
でいけました.
grid_table = AgGrid(...)
# "data" に Pandas DataFrame が帰ってくる
df = grid_table["data"]
Table のリロード
↑にあるように df をなんか処理してそれを反映したい...
AgGrid() に reload_data=True
設定が必要です.
基本は st.session_state
に DataFrame の情報を保存かなと思いますが, たとえばボタンで更新の場合, コードの評価は上から順に行われるため, 更新ボタンを下に配置するとうまくいきません(最初の変更が反映されない, 再描画になりレイアウトが一時的に崩れる)
おかしくなるケース
if not "df" in st.session_state:
df = pd.DataFrame(data)
st.session_state["df"] = df
df = st.session_state["df"]
gd = GridOptionsBuilder.from_dataframe(df)
gd_options = gd.build()
table = AgGrid(df, gridOptons=gd_options, reload_data=True)
if st.button("update"):
st.write("Bora")
df.iat[2, 2] += 1
アップデートさせたいときに st.experimental_rerun()
で強制的にリフレッシュさせるか, https://docs.streamlit.io/library/api-reference/control-flow/st.experimental_rerun
if st.button("update"):
st.write("Bora")
df.iat[2, 2] += 1
st.experimental_rerun() # force reload
以下のようにボタンを先に配置(df の編集を AgGrid を呼ぶまえに先に行う)すればいけます!
if not "df" in st.session_state:
df = pd.DataFrame(data)
st.session_state["df"] = df
df = st.session_state["df"]
gd = GridOptionsBuilder.from_dataframe(df)
gd_options = gd.build()
if st.button("update"):
st.write("Bora")
table.data.iat[2, 2] += 1
table = AgGrid(df, gridOptons=gd_options, reload_data=True)
ちなみに AgGrid() で key
引数指定しても st.session_state
には該当のステートは自動生成されませんでした
(2022/11/20 時点の最新版では作られるようになったっぽい)
浮動小数点数のフォーマット
configure_column(YourColumnNameHeader, type=[“numericColumn”,“numberColumnFilter”,“customNumericFormat”], precision=1)
でいけます. precision
には小数点以下の桁数を指定します. それ以上の凝ったことをやる場合は cellRenderer で html で頑張るしかないです
(ただ表示はうまくいくだろうが, 入力がうまくできなかも?)
テーブルの幅を自動 fit させる
AgGrid(..., fit_columns_on_grid_load=True, ...)
でいけます.
Cell に色をつける
ありがとうございます.
その他見た目を調整
手動で aggrid の設定 JSON を調整 and cellRenderer などで調整でしょうか...
EoL.