Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -9,13 +9,10 @@ import os
|
|
| 9 |
import matplotlib.font_manager as fm
|
| 10 |
|
| 11 |
# ============================
|
| 12 |
-
#
|
| 13 |
# ============================
|
| 14 |
|
| 15 |
-
#
|
| 16 |
-
font_path = "NotoSansTC-Regular.ttf" # 確認此檔案在工作目錄中
|
| 17 |
-
|
| 18 |
-
# 載入本地字型檔,並取得字型名稱
|
| 19 |
if os.path.exists(font_path):
|
| 20 |
fm.fontManager.addfont(font_path)
|
| 21 |
font_prop = fm.FontProperties(fname=font_path)
|
|
@@ -24,19 +21,20 @@ else:
|
|
| 24 |
st.error(f"找不到字型檔: {font_path}")
|
| 25 |
CHINESE_FONT = "Noto Sans TC" # fallback
|
| 26 |
|
| 27 |
-
#
|
| 28 |
pio.kaleido.scope.default_font = CHINESE_FONT
|
| 29 |
pio.kaleido.scope.default_format = "png"
|
| 30 |
pio.kaleido.scope.default_width = 1200
|
| 31 |
pio.kaleido.scope.default_height = 900
|
| 32 |
pio.kaleido.scope.default_scale = 2
|
| 33 |
-
|
| 34 |
-
# ★ 關鍵:告訴 Kaleido 額外字型的對應關係
|
| 35 |
pio.kaleido.scope.extra_fonts = {CHINESE_FONT: font_path}
|
| 36 |
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
def setup_chinese_font():
|
| 39 |
-
"""系統
|
| 40 |
try:
|
| 41 |
test_fig, ax = plt.subplots()
|
| 42 |
ax.text(0.5, 0.5, "中文測試", fontfamily=CHINESE_FONT)
|
|
@@ -47,17 +45,19 @@ def setup_chinese_font():
|
|
| 47 |
os.system('apt-get update && apt-get install -y --force-yes fonts-noto-cjk')
|
| 48 |
st.experimental_rerun()
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def load_data(uploaded_file):
|
| 51 |
-
"""
|
| 52 |
try:
|
| 53 |
df = pd.read_csv(uploaded_file, encoding='utf-8').dropna(how='all')
|
| 54 |
-
|
| 55 |
if '姓名' not in df.columns:
|
| 56 |
-
raise ValueError("CSV
|
| 57 |
-
|
| 58 |
numeric_columns = []
|
| 59 |
potential_numeric = ['平均', '總分', '國文', '英文', '數學', '自科',
|
| 60 |
-
|
| 61 |
for col in potential_numeric:
|
| 62 |
if col in df.columns:
|
| 63 |
try:
|
|
@@ -65,17 +65,19 @@ def load_data(uploaded_file):
|
|
| 65 |
numeric_columns.append(col)
|
| 66 |
except:
|
| 67 |
pass
|
| 68 |
-
|
| 69 |
if not numeric_columns:
|
| 70 |
-
raise ValueError("CSV
|
| 71 |
-
|
| 72 |
return df, numeric_columns
|
| 73 |
except Exception as e:
|
| 74 |
st.error(f"數據加載錯誤:{str(e)}")
|
| 75 |
return None, None
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
def create_radar_chart(df, selected_rows, selected_columns, student_name=""):
|
| 78 |
-
"""生成雷達圖"""
|
| 79 |
fig = go.Figure()
|
| 80 |
line_styles = ['solid', 'dot', 'dash', 'longdash', 'dashdot']
|
| 81 |
colors = ['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD']
|
|
@@ -131,8 +133,12 @@ def create_radar_chart(df, selected_rows, selected_columns, student_name=""):
|
|
| 131 |
)
|
| 132 |
return fig
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
def generate_radar_image(fig):
|
| 135 |
-
"""生成圖片 (
|
| 136 |
try:
|
| 137 |
# 重設 kaleido 配置(含 extra_fonts)
|
| 138 |
pio.kaleido.scope.default_font = CHINESE_FONT
|
|
@@ -181,6 +187,10 @@ def generate_radar_image(fig):
|
|
| 181 |
st.error(f"圖片生成失敗: {str(e)}")
|
| 182 |
return None
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
def main():
|
| 185 |
st.title('學生成績雷達圖產生器')
|
| 186 |
st.markdown("""
|
|
@@ -195,7 +205,7 @@ def main():
|
|
| 195 |
</style>
|
| 196 |
""", unsafe_allow_html=True)
|
| 197 |
|
| 198 |
-
setup_chinese_font() # 檢查字型
|
| 199 |
|
| 200 |
uploaded_file = st.file_uploader("上傳CSV檔案", type=['csv'])
|
| 201 |
if uploaded_file is not None:
|
|
@@ -232,6 +242,7 @@ def main():
|
|
| 232 |
target_students = [s for s in all_students if s not in current_comparison]
|
| 233 |
for idx, student in enumerate(target_students):
|
| 234 |
try:
|
|
|
|
| 235 |
fig = create_radar_chart(
|
| 236 |
df,
|
| 237 |
current_comparison + [student],
|
|
@@ -243,14 +254,14 @@ def main():
|
|
| 243 |
image_bytes[student] = img
|
| 244 |
except Exception as e:
|
| 245 |
st.error(f"生成 {student} 圖表失敗:{str(e)}")
|
| 246 |
-
progress_bar.progress((idx+1)/len(target_students))
|
| 247 |
if image_bytes:
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
zip_buffer = BytesIO()
|
| 255 |
with ZipFile(zip_buffer, "w") as zip_file:
|
| 256 |
for student, img in image_bytes.items():
|
|
@@ -265,4 +276,4 @@ def main():
|
|
| 265 |
)
|
| 266 |
|
| 267 |
if __name__ == "__main__":
|
| 268 |
-
main()
|
|
|
|
| 9 |
import matplotlib.font_manager as fm
|
| 10 |
|
| 11 |
# ============================
|
| 12 |
+
# 載入本地字型檔與設定
|
| 13 |
# ============================
|
| 14 |
|
| 15 |
+
font_path = "NotoSansTC-Regular.ttf" # 確認此字型檔路徑正確
|
|
|
|
|
|
|
|
|
|
| 16 |
if os.path.exists(font_path):
|
| 17 |
fm.fontManager.addfont(font_path)
|
| 18 |
font_prop = fm.FontProperties(fname=font_path)
|
|
|
|
| 21 |
st.error(f"找不到字型檔: {font_path}")
|
| 22 |
CHINESE_FONT = "Noto Sans TC" # fallback
|
| 23 |
|
| 24 |
+
# 配置 Kaleido 的預設參數與額外字型設定
|
| 25 |
pio.kaleido.scope.default_font = CHINESE_FONT
|
| 26 |
pio.kaleido.scope.default_format = "png"
|
| 27 |
pio.kaleido.scope.default_width = 1200
|
| 28 |
pio.kaleido.scope.default_height = 900
|
| 29 |
pio.kaleido.scope.default_scale = 2
|
|
|
|
|
|
|
| 30 |
pio.kaleido.scope.extra_fonts = {CHINESE_FONT: font_path}
|
| 31 |
|
| 32 |
+
# ============================
|
| 33 |
+
# 系統字型檢查與安裝
|
| 34 |
+
# ============================
|
| 35 |
|
| 36 |
def setup_chinese_font():
|
| 37 |
+
"""檢查系統中文字型是否可用,若無則嘗試安裝"""
|
| 38 |
try:
|
| 39 |
test_fig, ax = plt.subplots()
|
| 40 |
ax.text(0.5, 0.5, "中文測試", fontfamily=CHINESE_FONT)
|
|
|
|
| 45 |
os.system('apt-get update && apt-get install -y --force-yes fonts-noto-cjk')
|
| 46 |
st.experimental_rerun()
|
| 47 |
|
| 48 |
+
# ============================
|
| 49 |
+
# 資料讀取
|
| 50 |
+
# ============================
|
| 51 |
+
|
| 52 |
def load_data(uploaded_file):
|
| 53 |
+
"""從 CSV 檔案中讀取資料"""
|
| 54 |
try:
|
| 55 |
df = pd.read_csv(uploaded_file, encoding='utf-8').dropna(how='all')
|
|
|
|
| 56 |
if '姓名' not in df.columns:
|
| 57 |
+
raise ValueError("CSV檔案中缺少必要欄位:姓名")
|
|
|
|
| 58 |
numeric_columns = []
|
| 59 |
potential_numeric = ['平均', '總分', '國文', '英文', '數學', '自科',
|
| 60 |
+
'社會', '地理', '歷史', '公民', '物理', '化學', '生物']
|
| 61 |
for col in potential_numeric:
|
| 62 |
if col in df.columns:
|
| 63 |
try:
|
|
|
|
| 65 |
numeric_columns.append(col)
|
| 66 |
except:
|
| 67 |
pass
|
|
|
|
| 68 |
if not numeric_columns:
|
| 69 |
+
raise ValueError("CSV檔案中未找到有效的數值欄位")
|
|
|
|
| 70 |
return df, numeric_columns
|
| 71 |
except Exception as e:
|
| 72 |
st.error(f"數據加載錯誤:{str(e)}")
|
| 73 |
return None, None
|
| 74 |
|
| 75 |
+
# ============================
|
| 76 |
+
# 生成雷達圖
|
| 77 |
+
# ============================
|
| 78 |
+
|
| 79 |
def create_radar_chart(df, selected_rows, selected_columns, student_name=""):
|
| 80 |
+
"""生成雷達圖 (包含圖例、極座標軸等設定)"""
|
| 81 |
fig = go.Figure()
|
| 82 |
line_styles = ['solid', 'dot', 'dash', 'longdash', 'dashdot']
|
| 83 |
colors = ['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD']
|
|
|
|
| 133 |
)
|
| 134 |
return fig
|
| 135 |
|
| 136 |
+
# ============================
|
| 137 |
+
# 生成圖片
|
| 138 |
+
# ============================
|
| 139 |
+
|
| 140 |
def generate_radar_image(fig):
|
| 141 |
+
"""生成圖片 (重設 Kaleido 配置與更新字型設定)"""
|
| 142 |
try:
|
| 143 |
# 重設 kaleido 配置(含 extra_fonts)
|
| 144 |
pio.kaleido.scope.default_font = CHINESE_FONT
|
|
|
|
| 187 |
st.error(f"圖片生成失敗: {str(e)}")
|
| 188 |
return None
|
| 189 |
|
| 190 |
+
# ============================
|
| 191 |
+
# 主程式
|
| 192 |
+
# ============================
|
| 193 |
+
|
| 194 |
def main():
|
| 195 |
st.title('學生成績雷達圖產生器')
|
| 196 |
st.markdown("""
|
|
|
|
| 205 |
</style>
|
| 206 |
""", unsafe_allow_html=True)
|
| 207 |
|
| 208 |
+
setup_chinese_font() # 檢查中文字型
|
| 209 |
|
| 210 |
uploaded_file = st.file_uploader("上傳CSV檔案", type=['csv'])
|
| 211 |
if uploaded_file is not None:
|
|
|
|
| 242 |
target_students = [s for s in all_students if s not in current_comparison]
|
| 243 |
for idx, student in enumerate(target_students):
|
| 244 |
try:
|
| 245 |
+
# 動態組合比較組(比較基準加上當前學生)
|
| 246 |
fig = create_radar_chart(
|
| 247 |
df,
|
| 248 |
current_comparison + [student],
|
|
|
|
| 254 |
image_bytes[student] = img
|
| 255 |
except Exception as e:
|
| 256 |
st.error(f"生成 {student} 圖表失敗:{str(e)}")
|
| 257 |
+
progress_bar.progress((idx + 1) / len(target_students))
|
| 258 |
if image_bytes:
|
| 259 |
+
st.write("### 圖表預覽")
|
| 260 |
+
cols = st.columns(3)
|
| 261 |
+
for i, (student, img) in enumerate(image_bytes.items()):
|
| 262 |
+
with cols[i % 3]:
|
| 263 |
+
st.image(img, use_container_width=True)
|
| 264 |
+
st.caption(f"{student} 比較圖")
|
| 265 |
zip_buffer = BytesIO()
|
| 266 |
with ZipFile(zip_buffer, "w") as zip_file:
|
| 267 |
for student, img in image_bytes.items():
|
|
|
|
| 276 |
)
|
| 277 |
|
| 278 |
if __name__ == "__main__":
|
| 279 |
+
main()
|