File size: 15,432 Bytes
e04f1da 02b3da0 e04f1da 915dd60 6c273f9 af688b8 6c273f9 f6b39e3 d07506b 32bdeba d657b29 915dd60 d07506b 1d2f817 6d93cab d07506b 6d93cab d07506b 1d2f817 915dd60 6d93cab d07506b 6d93cab d07506b 6d93cab d07506b 6d93cab d07506b 915dd60 d07506b 6c273f9 8e97d0d 5ffd753 9e035c6 5ffd753 8e97d0d 5ffd753 8e97d0d 5ffd753 8e97d0d 5ffd753 8e97d0d 5ffd753 a042bc2 5ffd753 9e035c6 bcb1890 e04f1da 02b3da0 e04f1da 02b3da0 1f7b4f6 02b3da0 4c599cb 54f77f5 e04f1da 0c3f391 0c903a8 0c3f391 b963622 0c903a8 76447a7 0c3f391 af688b8 0c3f391 af688b8 76447a7 af688b8 0c3f391 e04f1da 0c3f391 e04f1da 76447a7 e04f1da 4c599cb 3ca060c 4c599cb 76447a7 0c903a8 76447a7 bcb1890 d07506b d657b29 6d93cab d07506b 6d93cab d07506b 6d93cab d07506b 6d93cab d657b29 d07506b bcb1890 d07506b e04f1da 5ffd753 32bdeba c5ef14c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 |
import streamlit as st
import pandas as pd
import requests
import json
from io import StringIO
import plotly.express as px
import qrcode
from PIL import Image
import io
# 自訂 CSS 確保標題不換行、調整圖片大小、優化按鈕布局、右下角資訊並調整頒獎台樣式
st.markdown("""
<style>
.stApp header h1 {
white-space: nowrap; /* 防止標題換行 */
overflow: hidden;
text-overflow: ellipsis;
font-size: 2rem; /* 調整字體大小為 2rem,約 32px */
}
.stImage > img {
max-height: 80vh; /* 圖片高度接近視窗高度 */
object-fit: contain; /* 保持比例 */
}
/* 調整按鈕樣式以確保對稱 */
.stButton > button {
width: 100px; /* 固定按鈕寬度 */
text-align: center; /* 文字居中 */
padding: 5px 10px; /* 調整內部間距 */
margin: 0 auto; /* 水平居中 */
}
/* 確保列寬度均等 */
.st-ds {
justify-content: space-between; /* 兩列間距均等 */
}
/* 右下角資訊 */
.footer {
position: fixed;
bottom: 10px;
right: 10px;
font-size: 0.9rem;
color: #666;
padding: 5px;
}
/* 頒獎台樣式(正常顯示) */
.podium-container {
display: flex;
justify-content: center;
align-items: flex-end; /* 確保下緣對齊 */
margin-top: 20px;
margin-bottom: 20px;
}
.podium {
text-align: center;
width: 120px;
margin: 0 10px;
display: flex;
flex-direction: column;
}
.podium-filler {
background-color: #1a1a1a; /* 與背景同色 */
flex-grow: 0;
}
.podium-step {
border: 2px solid #000;
color: #333;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
flex-grow: 0;
}
.podium-step.first {
height: 150px; /* 第一名完整高度 */
background-color: #ffd700; /* 金色 */
}
.podium-step.second {
height: 120px; /* 第二名高度 */
background-color: #c0c0c0; /* 銀色 */
}
.podium-step.third {
height: 90px; /* 第三名高度 */
background-color: #cd7f32; /* 銅色 */
}
.podium-label {
margin-top: 5px;
font-size: 1rem;
color: #fff; /* 文字改為白色 */
border: 2px solid #fff; /* 添加白色邊框 */
padding: 5px; /* 內距 */
background-color: rgba(0, 0, 0, 0.5); /* 半透明背景,提升可讀性 */
white-space: pre-line; /* 允許換行 */
}
</style>
""", unsafe_allow_html=True)
# 圓周率與無限猴子定理的介紹與分頁圖片展示
st.title("探索圓周率與無限猴子定理的奇妙世界")
st.write("""
歡迎參加這個有趣的活動!我們將一起探索圓周率 (π) 和無限猴子定理的奧秘,並透過幸運數字查詢系統,發現你選的數字在 π 中的秘密。
""")
# 定義圖片和標題
image_data = [
{"path": "m1.GIF", "caption": "圓周率與無限猴子定理的奇妙關聯"},
{"path": "m2.GIF", "caption": "重複與不重複"},
{"path": "m3.GIF", "caption": "圓周率裡藏著你的幸運號碼"},
{"path": "m4.GIF", "caption": "無限猴子定理是什麼?"},
{"path": "m5.GIF", "caption": "圓周率是無限長的數字打字機"},
]
# 初始化 session state 用於追蹤當前頁面
if 'page' not in st.session_state:
st.session_state.page = 0
# 顯示當前頁面的圖片,放大至幾乎全版面
st.image(image_data[st.session_state.page]["path"],
caption=image_data[st.session_state.page]["caption"],
use_container_width=True)
# 換頁按鈕
col1, col2 = st.columns(2)
with col1:
if st.button("上一頁", disabled=st.session_state.page <= 0):
st.session_state.page -= 1
st.rerun()
with col2:
if st.button("下一頁", disabled=st.session_state.page >= len(image_data) - 1):
st.session_state.page += 1
st.rerun()
# 顯示當前頁數
st.write(f"頁面: {st.session_state.page + 1} / {len(image_data)}")
# 分隔線
st.markdown("---")
# 查詢數字的函數
def process_number(number):
"""查詢特定數字並返回結果"""
url = f"https://www.angio.net/newpi/piquery?q={number}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
# 從回應中提取 'p' 值
if data.get('status') == 'OK' and data.get('r'):
return data['r'][0].get('p')
return None
except Exception as e:
st.error(f"處理數字 {number} 時發生錯誤: {str(e)}")
return None
def generate_qr_code(url):
"""生成 QR Code 並返回圖片"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
return img
def main():
st.title("幸運數字查詢系統")
# 新增功能:輸入表單連結並生成 QR Code
st.subheader("請輸入 Google 表單連結")
form_url = st.text_input("表單連結", placeholder="請輸入 Google 表單的完整 URL")
if form_url:
try:
# 驗證 URL 是否有效(簡單檢查)
if not form_url.startswith("https://"):
st.error("請輸入有效的 URL(以 https:// 開頭)")
else:
st.write("以下是表單連結的 QR Code,學生可以使用平板掃描填寫:")
# 生成 QR Code
qr_img = generate_qr_code(form_url)
# 將 PIL 圖片轉為字節流以顯示在 Streamlit
buf = io.BytesIO()
qr_img.save(buf, format="PNG")
byte_im = buf.getvalue()
st.image(byte_im, caption="掃描此 QR Code 前往表單", use_container_width=False)
except Exception as e:
st.error(f"生成 QR Code 時發生錯誤: {str(e)}")
# 分隔線
st.markdown("---")
# 讓使用者選擇幸運數字的位數
selected_digits = st.selectbox(
"請選擇幸運數字的位數",
options=range(4, 11), # 4 到 10 位
index=3 # 預設選 7 位
)
# 檔案上傳部分
uploaded_file = st.file_uploader("請上傳CSV檔案", type=['csv'])
if uploaded_file:
try:
# 嘗試讀取 CSV,假設有表頭
df = pd.read_csv(
StringIO(uploaded_file.getvalue().decode('utf-8')),
header=0, # 預設有表頭
dtype=str # 所有欄位都以字串形式讀取
)
# 定義可能的欄位映射
required_columns = {
"您的姓名": "姓名",
"你的幸運號碼是?(可重複,前面也可以是0。Ex. 0000013、1111111)": "幸運號碼",
"姓名": "姓名",
"自選號碼": "幸運號碼"
}
# 檢查是否有必要的欄位
available_columns = [col for col in required_columns.keys() if col in df.columns]
if not available_columns or len(available_columns) < 2:
# 若無表頭或缺少必要欄位,假設無表頭,第一列為姓名,第二列為幸運號碼
df = pd.read_csv(
StringIO(uploaded_file.getvalue().decode('utf-8')),
header=None,
dtype=str
)
if len(df.columns) >= 2:
df.columns = ["姓名", "幸運號碼"]
else:
st.error("CSV 檔案格式錯誤,缺少 '姓名' 或 '幸運號碼' 欄位。")
return
# 篩選並重新命名需要的欄位,忽略「時間戳記」
df = df[[col for col in df.columns if col in required_columns]].rename(columns=required_columns)
# 顯示原始資料
st.subheader("原始資料")
st.dataframe(df)
# 處理每個幸運數字
st.subheader("處理結果")
# 建立進度條
progress_bar = st.progress(0)
status_text = st.empty()
# 新增結果欄位
df['查詢結果'] = None
# 處理每個數字
total_rows = len(df)
for index, row in df.iterrows():
# 更新進度
progress = (index + 1) / total_rows
progress_bar.progress(progress)
status_text.text(f"正在處理: {index + 1}/{total_rows}")
# 獲取幸運數字並檢查位數
original_number = str(row['幸運號碼'])
number_length = len(original_number)
if number_length > selected_digits:
# 超過指定位數,標記為不符規定
df.at[index, '幸運號碼'] = original_number # 保留原始輸入
df.at[index, '查詢結果'] = "數字不符規定"
else:
# 小於或等於指定位數,補零後查詢
lucky_number = original_number.zfill(selected_digits)
result = process_number(lucky_number)
df.at[index, '幸運號碼'] = lucky_number # 更新為補零後的數字
df.at[index, '查詢結果'] = result
# 顯示結果
st.subheader("完整結果")
st.dataframe(df)
# 提供下載功能,確保輸出時保留前導零
csv = df.to_csv(index=False, encoding='utf-8')
st.download_button(
label="下載處理結果",
data=csv,
file_name="processed_results.csv",
mime="text/csv"
)
# 新增長條圖功能
st.subheader("查詢結果長條圖")
# 篩選有效數據(查詢結果不為 "數字不符規定" 且不為 None)
valid_df = df[df['查詢結果'].notna() & (df['查詢結果'] != "數字不符規定")].copy()
if not valid_df.empty:
# 將查詢結果轉為數值型
valid_df['查詢結果'] = valid_df['查詢結果'].astype(float)
# 拼接姓名和幸運號碼作為橫軸標籤
valid_df['姓名_幸運號碼'] = valid_df['姓名'] + "_" + valid_df['幸運號碼']
# 按照查詢結果從大到小排序
valid_df = valid_df.sort_values(by='查詢結果', ascending=False)
# 使用 Plotly 繪製長條圖
fig = px.bar(
valid_df,
x='姓名_幸運號碼',
y='查詢結果',
labels={'姓名_幸運號碼': '姓名與幸運號碼', '查詢結果': '查詢結果數值'},
title="幸運數字查詢結果長條圖(按數值從大到小排序)"
)
# 調整圖表佈局
fig.update_layout(
xaxis_title="姓名與幸運號碼",
yaxis_title="查詢結果數值",
xaxis_tickangle=-45, # 旋轉橫軸標籤以避免重疊
margin=dict(l=50, r=50, t=80, b=150) # 增加底部邊距以顯示完整標籤
)
# 顯示圖表
st.plotly_chart(fig, use_container_width=True)
# 新增頒獎台功能
st.subheader("頒獎台:查詢結果前三名")
# 獲取前三名(已按查詢結果降序排序)
top_3 = valid_df.head(3)
if len(top_3) > 0:
# 正常顯示頒獎台
col1, col2, col3 = st.columns(3) # 創建三列
with col1:
if len(top_3) >= 2:
second = top_3.iloc[1]
st.markdown(
f'<div class="podium">'
f'<div class="podium-filler" style="height: 30px;"></div>' # 填充 30px
f'<div class="podium-step second">{int(second["查詢結果"])}</div>'
f'<div class="podium-label">{second["姓名"]}\n{second["幸運號碼"]}</div>'
f'</div>',
unsafe_allow_html=True
)
else:
st.markdown('<div class="podium"><div class="podium-filler" style="height: 30px;"></div><div class="podium-step second"></div><div class="podium-label"></div></div>', unsafe_allow_html=True)
with col2:
if len(top_3) >= 1:
first = top_3.iloc[0]
st.markdown(
f'<div class="podium">'
f'<div class="podium-step first">{int(first["查詢結果"])}</div>'
f'<div class="podium-label">{first["姓名"]}\n{first["幸運號碼"]}</div>'
f'</div>',
unsafe_allow_html=True
)
else:
st.markdown('<div class="podium"><div class="podium-step first"></div><div class="podium-label"></div></div>', unsafe_allow_html=True)
with col3:
if len(top_3) >= 3:
third = top_3.iloc[2]
st.markdown(
f'<div class="podium">'
f'<div class="podium-filler" style="height: 60px;"></div>' # 填充 60px
f'<div class="podium-step third">{int(third["查詢結果"])}</div>'
f'<div class="podium-label">{third["姓名"]}\n{third["幸運號碼"]}</div>'
f'</div>',
unsafe_allow_html=True
)
else:
st.markdown('<div class="podium"><div class="podium-filler" style="height: 60px;"></div><div class="podium-step third"></div><div class="podium-label"></div></div>', unsafe_allow_html=True)
else:
st.warning("無有效的查詢結果可供顯示頒獎台。")
else:
st.warning("無有效的查詢結果可供繪製長條圖或頒獎台。請確認資料是否正確。")
except Exception as e:
st.error(f"處理檔案時發生錯誤: {str(e)}")
# 添加右下角資訊
st.markdown('<div class="footer">程式設計:新竹縣立精華國中 藍星宇老師</div>', unsafe_allow_html=True)
if __name__ == "__main__":
main() |