import streamlit as st import pandas as pd import plotly.graph_objects as go import os from zipfile import ZipFile import plotly.io as pio from PIL import Image, ImageDraw from io import BytesIO import numpy as np import os import shutil try: import kaleido except ImportError: st.error("請安裝 'kaleido' 套件以啟用圖像導出功能:\n\n $ pip install kaleido") def load_data(uploaded_file): """載入並處理CSV檔案""" try: # 直接載入檔案 df = pd.read_csv(uploaded_file, encoding='utf-8') # 移除空白列 df = df.dropna(how='all') # 將數值欄位轉換為數字類型 numeric_columns = ['平均', '總分', '國文', '英文', '數學', '自科', '社會', '地理', '歷史', '公民'] for col in numeric_columns: df[col] = pd.to_numeric(df[col], errors='coerce') return df except Exception as e: st.error(f"載入檔案時發生錯誤:{e}") return None def create_radar_chart(df, selected_rows, selected_columns): """使用Plotly建立雷達圖""" line_styles = ['solid', 'dot', 'dash', 'longdash', 'dashdot'] colors = ['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD'] fig = go.Figure() for i, row_name in enumerate(selected_rows): row_data = df[df['姓名'] == row_name][selected_columns].iloc[0] fig.add_trace(go.Scatterpolar( r=row_data.values, theta=selected_columns, fill='toself', name=row_name, line=dict( color=colors[i % len(colors)], dash=line_styles[i % len(line_styles)], width=2 ), marker=dict(opacity=0.5) )) if selected_columns: max_value = df[selected_columns].values.max() * 1.1 else: max_value = 100 fig.update_layout( polar=dict( radialaxis=dict( visible=True, range=[0, max_value], tickfont=dict(size=12, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial") ), angularaxis=dict( tickfont=dict(size=16, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial") ) ), showlegend=True, legend=dict( font=dict(size=14, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial") ), title='學生成績雷達圖', plot_bgcolor='white', paper_bgcolor='white', font=dict(family="Microsoft JhengHei, Noto Sans CJK, Arial") ) return fig def apply_font_to_all_text(fig): """強制設定圖表內所有文字元素的字型""" for trace in fig.data: if hasattr(trace, 'textfont'): trace.textfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" if hasattr(trace, 'marker') and hasattr(trace.marker, 'textfont'): trace.marker.textfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" fig.update_layout( font=dict( family="Microsoft JhengHei, Noto Sans CJK, Arial" ) ) if hasattr(fig, 'layout') and hasattr(fig.layout, 'xaxis'): fig.layout.xaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" if hasattr(fig, 'layout') and hasattr(fig.layout, 'yaxis'): fig.layout.yaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" if hasattr(fig, 'layout') and hasattr(fig.layout, 'polar') and hasattr(fig.layout.polar, 'radialaxis'): fig.layout.polar.radialaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" if hasattr(fig, 'layout') and hasattr(fig.layout, 'polar') and hasattr(fig.layout.polar, 'angularaxis'): fig.layout.polar.angularaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial" return fig def save_radar_chart_image(fig): """使用 kaleido 輸出 png 的記憶體檔案""" img_bytes = pio.to_image(fig, format="png", engine="kaleido") return img_bytes def create_composite_image(fig, student): """使用 PIL 合成圖片,確保學生的成績在最上層""" img_bytes = pio.to_image(fig, format="png", engine="kaleido") img = Image.open(BytesIO(img_bytes)).convert("RGBA") background = Image.new('RGBA', img.size, (255, 255, 255, 255)) composite = Image.alpha_composite(background, img) return composite def main(): st.title('學生成績雷達圖產生器') uploaded_file = st.file_uploader("上傳CSV檔案", type=['csv']) if uploaded_file is not None: df = load_data(uploaded_file) if df is not None: numeric_columns = ['平均', '總分', '國文', '英文', '數學', '自科', '社會', '地理', '歷史', '公民'] st.write("### 選擇要比較的欄位") selected_columns = [col for col in numeric_columns if st.checkbox(col, key=col)] st.write("### 選擇要比較的對象") selected_rows = st.multiselect('選擇要比較的對象', df['姓名'].tolist()) if selected_columns and selected_rows: try: fig = create_radar_chart(df, selected_rows, selected_columns) st.plotly_chart(fig, use_container_width=True) except Exception as e: st.error(f"生成雷達圖時發生錯誤:{e}") st.write("### 批次繪製個別學生比較圖") individual_students = st.multiselect("選擇要個別比較的學生", df['姓名'].tolist(), key = "student") comparison_items = st.multiselect("選擇要比較的項目", df['姓名'].tolist(), key = "item") if individual_students and comparison_items: image_options = [] image_bytes = {} for student in individual_students: fig = create_radar_chart(df, [student] + comparison_items, selected_columns) image_bytes[student] = create_composite_image(fig,student) image_options.append(f"{student} 與 {', '.join(comparison_items)} 的比較") selected_image_options = st.multiselect("選擇要顯示的圖片", options=image_options) cols = st.columns(3) # 排成三列 for i, option in enumerate(selected_image_options): student = option.split(" 與 ")[0] with cols[i%3]: st.image(image_bytes[student], use_container_width=True) st.text(option) if __name__ == "__main__": import numpy as np main()