Spaces:
Sleeping
Sleeping
| 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() |