おねーちゃん!今回はUIの整理ってことだけど、どんな感じで整理するの?
そうだねー
じゃあ、まずは検索する機能を別の場所に置こうか!
別の場所??検索する機能は1番上にあった方が良くない??
Streamlitは、メインの画面以外に、その画面の左側の「サイドバー」に配置する仕組みもあるんだ
ほうほう
サイドバーを使うとどんな感じになるの?
それは「百聞は一見に如かず」っていうし、実際にプログラムを変更して確認してみよう!
「st.input()」を「st.sidebar.input()」って感じで「.sidebar」を追加するだけで変更できるよ
# タイトル・説明
st.sidebar.title("ポケモン図鑑")
st.sidebar.caption("このサイトはポケモン非公式ファン作品です。画像・音声は株式会社ポケモンおよび関連各社の著作物に基づいています。学習目的のみに使用し、無断転載・商用利用・再配布は禁止されています。")
# 入力とボタン
user_input = st.sidebar.text_input("ポケモンの名前またはIDを入力してください")
search_btn = st.sidebar.button("検索")
サイドバーに変更する時はそれだけで良いんだ!
サイドバーって開閉できるんだ!結構便利な機能だね
次は画像を切り替えられるように「タブ切り替え」を出来るようにしようか
タブ切り替え?
こんな感じのやつだね
おお、なるほど!!この切り替える仕組みがStreamlitなら簡単に作れるんだね!!
Streamlitの場合は「st.tabs(配列)」を使うことで切り替えができるよ
画像の表示部分を次のプログラムに書き換えてみてね!
# タブで画像表示
tab1, tab2, tab3 = st.tabs(["通常", "色違い", "アートワーク"])
with tab1:
st.image(data["sprites"]["front_default"], caption="通常", width=200)
with tab2:
st.image(data["sprites"]["front_shiny"], caption="色違い", width=200)
with tab3:
st.image(data["sprites"]["other"]["official-artwork"]["front_default"], caption="公式アート", width=200)
おお!ちゃんと切り替わってる!!
「st.tabs()」を使う場合は、その選択肢と同じ数の「with」で管理する形にすればOKだから簡単でしょ!
とりあえずはこれで完成だけど、何か他に欲しい機能はある??
じゃあ、ランダムにポケモンが表示するってしてみたい!
おおーいいね!そしたら、
1.「ランダム表示」と書かれたボタンをサイドバーに用意
2.押された時に「ID_TO_NAME」のキーからランダムで1つ選択して「user_input」に代入
3.「search_btn」をTrueにする
って流れで作ってみよう
import random # 追加を忘れずに!!
# 途中は省略
# 入力とボタン
user_input = st.sidebar.text_input("ポケモンの名前またはIDを入力してください")
search_btn = st.sidebar.button("検索")
random_btn = st.sidebar.button("ランダム表示")
# ランダムボタンが押された時の処理
if random_btn:
user_input = random.choice(list(ID_TO_NAME.keys()))
search_btn = True
このプログラムを「if search_btn:」の行の上に作る事で、すでに作ってる検索ボタンの処理と連動できるようになるよ!
ランダムで表示するボタンは「IDをランダムで選ぶ」以外の動きが検索ボタンと同じで良いし、「search_btn」は元々検索ボタンが押されたかどうかでTrueかFalseに切り替わるだけだから、直接変更しても良いんだね!
おお!押すたびにちゃんといろんなポケモンが出てくる!!ただランダムで出てくるだけでも面白いね
ちなみにおねーちゃん!Streamlitって1ページしか作れないの??
いや、そんなことはないよ!もし複数ページ作りたい場合は、今作ってるプログラムが置かれている場所と同じフォルダの中に「pages」っていうフォルダを作って、その中に新しいページのプログラムを作れば勝手に対応してくれるよ!
え、それだけ!?
じゃあ、次のプログラムを「About.py」って名前にして「pages」フォルダの中に入れた後、もう一回「streamlit run app.py」で起動し直してみて!
import streamlit as st
st.title("この図鑑について")
st.markdown("""
### このアプリは何?
このWebアプリは、**PokeAPI** を使ってポケモンの情報を検索できる、
**Streamlit製の学習用アプリ**です。
- 使用技術:Python / Streamlit / REST API
- 想定対象:中学生〜高校生
- 学習内容:APIの使い方・辞書の扱い・UI設計
---
### 著作権について
- このアプリは **ポケモン非公式のファン作品**です。
- キャラクターや名称は **株式会社ポケモン** および **関連各社の著作物**です。
- 学習目的以外での使用、特に **商用利用・転載・画像配布は禁止**されています。
""")
pagesフォルダの中にある「〇〇.py」の「〇〇」部分がページの名前になってサイドバーに追加されてる...!!
サイドバーに出てきた項目をクリックすると、ちゃんとページが切り替わってるでしょ!
すごく簡単にページが増やせるのは良いね!!
さて、無事良い感じにページが完成したことだし、次回は完成したページを誰でも見れるように公開してみよう!!
この時点での「app.py」のプログラム
import streamlit as st
import json
import requests
import random
# タイプと能力値の日本語辞書
TYPES = {
"normal": "ノーマル", "fire": "ほのお", "water": "みず", "electric": "でんき",
"grass": "くさ", "ice": "こおり", "fighting": "かくとう", "poison": "どく",
"ground": "じめん", "flying": "ひこう", "psychic": "エスパー", "bug": "むし",
"rock": "いわ", "ghost": "ゴースト", "dragon": "ドラゴン", "dark": "あく",
"steel": "はがね", "fairy": "フェアリー"
}
STATS = {
"hp": "HP", "attack": "こうげき", "defense": "ぼうぎょ",
"special-attack": "とくこう", "special-defense": "とくぼう",
"speed": "すばやさ"
}
# JSONファイルを読み込み(日本語名 → ID)
with open("poke_dict.json", "r", encoding="utf-8") as f:
NAME_TO_ID = json.load(f)
# ID → 日本語名 の逆引き辞書も作成
ID_TO_NAME = {str(v): k for k, v in NAME_TO_ID.items()}
# タイトル・説明
st.sidebar.title("ポケモン図鑑")
st.sidebar.caption("このサイトはポケモン非公式ファン作品です。画像・音声は株式会社ポケモンおよび関連各社の著作物に基づいています。学習目的のみに使用し、無断転載・商用利用・再配布は禁止されています。")
# 入力とボタン
user_input = st.sidebar.text_input("ポケモンの名前またはIDを入力してください")
search_btn = st.sidebar.button("検索")
random_btn = st.sidebar.button("ランダム表示")
# ランダムボタンが押された時の処理
if random_btn:
user_input = random.choice(list(ID_TO_NAME.keys()))
search_btn = True
# 検索処理
if search_btn:
poke_id = None
poke_name = None
if user_input in NAME_TO_ID:
poke_id = NAME_TO_ID[user_input]
poke_name = user_input
elif user_input in ID_TO_NAME:
poke_id = int(user_input)
poke_name = ID_TO_NAME[user_input]
if poke_id is not None:
api_url = f"https://pokeapi.co/api/v2/pokemon/{poke_id}"
res = requests.get(api_url)
if res.status_code == 200:
data = res.json()
height = data["height"] / 10
weight = data["weight"] / 10
# タイプを日本語に変換
types = [TYPES[t["type"]["name"]] for t in data["types"]]
# 種族値を日本語に変換
stats = {STATS[s["stat"]["name"]]: s["base_stat"] for s in data["stats"]}
st.header(f"{poke_name}")
st.write(f"ID: {poke_id}")
st.write(f"タイプ: {', '.join(types)}")
st.write(f"たかさ: {height} m")
st.write(f"おもさ: {weight} kg")
st.subheader("種族値")
for stat_name, stat_val in stats.items():
st.write(f"- {stat_name}: {stat_val}")
# タブで画像表示
tab1, tab2, tab3 = st.tabs(["通常", "色違い", "アートワーク"])
with tab1:
st.image(data["sprites"]["front_default"], caption="通常", width=200)
with tab2:
st.image(data["sprites"]["front_shiny"], caption="色違い", width=200)
with tab3:
st.image(data["sprites"]["other"]["official-artwork"]["front_default"], caption="公式アート", width=200)
# 鳴き声の再生
cry_url = data.get("cries", {}).get("latest")
if cry_url:
st.subheader("鳴き声")
st.audio(cry_url)
else:
st.info("このポケモンには鳴き声が登録されていません。")
else:
st.error("その名前やIDのポケモンは見つかりませんでした。")
