5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Streamlit] 列ごとにスクロールさせる方法

Posted at

この記事では、Streamlitで作った列に対してCSSを設定し、列ごとにスクロールさせる方法について調べたことをまとめています。
Streamlitでは、columnsを使って列を複数作ることができるのですが、列ごとに表示する内容の長さが異なる場合、見えにくくなることがあります。
例えば下記のコードのように2列を作り、1列目に1行のテキスト、2列目には1から100の数字を縦に表示するとします。

import streamlit as st

col1, col2 = st.columns(2)
col1.write('1から100までの数字を表示します。')

for i in range(100):
    col2.write(i)

スクロールしなければ1列目のテキストは見えますが、
image.png

スクロールしてしまうと1列目のテキストが見えなくなってしまいます。
image.png

そこで、列ごとに縦にスクロールさせる方法を検討してみました。
調べてみたところ、デフォルトでそのような機能は無いようなので、CSSを書きmarkdownに設定する必要があるだろうと思いました。
Streamlitのウィジェットにcssをあてる方法も調べてみたのですが、どうすれば列に対してCSSを設定できるかの情報は見当たりませんでした。
ですので、すでにCSSをあてる方法を試している方の記事を参考に、どうすれば列にCSSを設定できるのかを調べてみることにしました。

まず、こちらの記事を参考に、ボタンに対してcssをあてるというのを試してみました。
記事通りの実装で、期待通りにcssが反映されました。
この記事ではstButtonというクラス名に対してcssを設定していたので、htmlを出力して実際にどこにスタイルが当たっているかを確認してみました。
すると、下記のようにクラス名にstButtonが見つかりました。(2行目のdiv)

    <div data-stale="false" width="704" class="element-container css-1hynsf2 esravye2">
      <div class="row-widget stButton" style="width: 704px;">
        <button kind="secondary" class="css-7ym5gk e1ewe7hr10">
          <div data-testid="stMarkdownContainer" class="css-1vbkxwb eqr7zpz4">
            <p>このボタンを押してください</p>
          </div>
        </button>
      </div>
    </div>
  </div>
</div>
<div class="resize-triggers">
  <div class="expand-trigger">
    <div style="width: 737px; height: 317px;"></div>
  </div>
  <div class="contract-trigger"></div>
</div>

続いて、このコードを改良し、列を設定してみました。

import streamlit as st

button_css = f"""
<style>
  div.stButton > button:first-child  {{
    font-weight  : bold                ;/* 文字:太字                   */
    border       :  5px solid #f36     ;/* 枠線:ピンク色で5ピクセルの実線 */
    border-radius: 10px 10px 10px 10px ;/* 枠線:半径10ピクセルの角丸     */
    background   : #ddd                ;/* 背景色:薄いグレー            */
  }}
</style>
"""
st.markdown(button_css, unsafe_allow_html=True)

col1, col2 = st.columns[2]
action = col1.button('このボタンを押してください')
text = col2.text('ここは2列目です')

image.png

先ほどと同様に、このコードで表示されたhtmlを確認してみます。
すると下記のように差が出ました。

<div data-testid="stHorizontalBlock" class="css-ocqkz7 esravye3">
  <div data-testid="column" class="css-keje6w esravye1" style="position: relative;">
    <div style="overflow: visible; width: 0px; display: flex; flex-direction: column; flex: 1 1 0%;">
      <div width="338" data-testid="stVerticalBlock" class="css-4fbub3 esravye0">
        <div data-stale="false" width="338" class="element-container css-16x3zmr esravye2">
          <div class="row-widget stButton" style="width: 338px;">
            <button kind="secondary" class="css-7ym5gk e1ewe7hr10">
              <div data-testid="stMarkdownContainer" class="css-1vbkxwb eqr7zpz4">
                <p>このボタンを押してください</p>
              </div>
            </button>
          </div>
        </div>
      </div>
    </div>
    <div class="resize-triggers">
      <div class="expand-trigger">
        <div style="width: 339px; height: 45px;"></div>
      </div>
      <div class="contract-trigger"></div>
    </div>
  </div>
  <div data-testid="column" class="css-keje6w esravye1" style="position: relative;">
    <div style="overflow: visible; width: 0px; display: flex; flex-direction: column; flex: 1 1 0%;">
      <div width="338" data-testid="stVerticalBlock" class="css-4fbub3 esravye0">
        <div data-stale="false" width="338" class="element-container css-16x3zmr esravye2">
          <div class="stTextLabelWrapper css-y4bq5x e10yodom1" style="width: 338px;">
            <div data-testid="stText" class="css-183lzff e170wjxx0">ここは2列目です</div>
          </div>
        </div>
      </div>
    </div>
    <div class="resize-triggers">
      <div class="expand-trigger">
        <div style="width: 339px; height: 45px;"></div>
      </div>
      <div class="contract-trigger"></div>
    </div>
  </div>
</div>

このhtmlから、クラス名が"css-keje6w esravye1"となっている部分(2行目、23行目のdiv)が列を表していると考えられます。
しかし、このクラス名では明確に列であることを表しているようには見えませんでした。
しかし、data-testid属性がcolumnとなっており、これを使うと列として特定できるのではないかと考えました。
ですので、この属性に対してcssをあてることにしました。
(本来data-testid属性はテストに使う属性ですので、適切な使い方ではないと思いますが。。)
具体的には下記のように設定しています。

import streamlit as st

button_css = f"""
<style>
  div.stButton > button:first-child  {{
    font-weight  : bold                ;/* 文字:太字                   */
    border       :  5px solid #f36     ;/* 枠線:ピンク色で5ピクセルの実線 */
    border-radius: 10px 10px 10px 10px ;/* 枠線:半径10ピクセルの角丸     */
    background   : #ddd                ;/* 背景色:薄いグレー            */
  }}
  div[data-testid="column"] {{
    background   : #bbb                ;
  }}
</style>
"""
st.markdown(button_css, unsafe_allow_html=True)

col1, col2 = st.columns(2)
action = col1.button('このボタンを押してください')
text = col2.text('ここは2列目です')

これを確認してみると、確かに列ごとにcssが反映されていることが確認できました!
image.png

それでは、本題の列ごとのスクロールを試してみます。
下記のようなコードにしました。
一つ注意点として、heightも指定する必要があります。
高さを設定しないと、列の高さがすべての要素分の高さとなってしまい、ブラウザ全体のスクロールになってしまいます。

import streamlit as st

css = f"""
<style>
  div[data-testid="column"] {{
    overflow: scroll;
    height: 300px;
  }}
</style>
"""
st.markdown(css, unsafe_allow_html=True)

col1, col2 = st.columns(2)
col1.write('1から100までの数字を表示します。')

for i in range(100):
    col2.write(i)

これをブラウザで確認してみます。
期待通り、列ごとにスクロールすることができました!

image.png

課題

今回の方法では、すべての列に対して同じCSSが反映されてしまいます。
今回の例ではcol2のみスクロールができればよかったのですが、col1にもoverflowのCSSが設定されているようでした。
stでなくcol2にmarkdownを設定しても、結果は変わりませんでした。
また、stremalitのウィジェットはkeyを設定することで識別できるのですが、
keyの値をhtmlで検索してみても見つからず、htmlに反映されているわけではないようでした。
今後、特定の列のみにcssを反映させる方法についても調査してみたいと思います。

参考文献

5
6
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
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?