ChoCho66/radar_chart
0
1import streamlit as st2import pandas as pd3import plotly.graph_objects as go4import os5from zipfile import ZipFile6import plotly.io as pio7from PIL import Image, ImageDraw8from io import BytesIO9import numpy as np10import os11import shutil12 13try:14 import kaleido15except ImportError:16 st.error("請安裝 'kaleido' 套件以啟用圖像導出功能:\n\n $ pip install kaleido")17 18def load_data(uploaded_file):19 """載入並處理CSV檔案"""20 try:21 # 直接載入檔案22 df = pd.read_csv(uploaded_file, encoding='utf-8')23 24 # 移除空白列25 df = df.dropna(how='all')26 27 # 將數值欄位轉換為數字類型28 numeric_columns = ['平均', '總分', '國文', '英文', '數學', '自科', '社會', '地理', '歷史', '公民']29 for col in numeric_columns:30 df[col] = pd.to_numeric(df[col], errors='coerce')31 32 return df33 except Exception as e:34 st.error(f"載入檔案時發生錯誤:{e}")35 return None36 37def create_radar_chart(df, selected_rows, selected_columns):38 """使用Plotly建立雷達圖"""39 line_styles = ['solid', 'dot', 'dash', 'longdash', 'dashdot']40 colors = ['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD']41 42 fig = go.Figure()43 44 for i, row_name in enumerate(selected_rows):45 row_data = df[df['姓名'] == row_name][selected_columns].iloc[0]46 47 fig.add_trace(go.Scatterpolar(48 r=row_data.values,49 theta=selected_columns,50 fill='toself',51 name=row_name,52 line=dict(53 color=colors[i % len(colors)],54 dash=line_styles[i % len(line_styles)],55 width=256 ),57 marker=dict(opacity=0.5)58 ))59 if selected_columns:60 max_value = df[selected_columns].values.max() * 1.161 else:62 max_value = 10063 64 fig.update_layout(65 polar=dict(66 radialaxis=dict(67 visible=True,68 range=[0, max_value],69 tickfont=dict(size=12, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial")70 ),71 angularaxis=dict(72 tickfont=dict(size=16, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial")73 )74 ),75 showlegend=True,76 legend=dict(77 font=dict(size=14, color='black', family="Microsoft JhengHei, Noto Sans CJK, Arial")78 ),79 title='學生成績雷達圖',80 plot_bgcolor='white',81 paper_bgcolor='white',82 font=dict(family="Microsoft JhengHei, Noto Sans CJK, Arial")83 )84 85 return fig86 87def apply_font_to_all_text(fig):88 """強制設定圖表內所有文字元素的字型"""89 for trace in fig.data:90 if hasattr(trace, 'textfont'):91 trace.textfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"92 if hasattr(trace, 'marker') and hasattr(trace.marker, 'textfont'):93 trace.marker.textfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"94 fig.update_layout(95 font=dict(96 family="Microsoft JhengHei, Noto Sans CJK, Arial"97 )98 )99 100 if hasattr(fig, 'layout') and hasattr(fig.layout, 'xaxis'):101 fig.layout.xaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"102 if hasattr(fig, 'layout') and hasattr(fig.layout, 'yaxis'):103 fig.layout.yaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"104 if hasattr(fig, 'layout') and hasattr(fig.layout, 'polar') and hasattr(fig.layout.polar, 'radialaxis'):105 fig.layout.polar.radialaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"106 if hasattr(fig, 'layout') and hasattr(fig.layout, 'polar') and hasattr(fig.layout.polar, 'angularaxis'):107 fig.layout.polar.angularaxis.tickfont.family = "Microsoft JhengHei, Noto Sans CJK, Arial"108 109 return fig110 111def save_radar_chart_image(fig):112 """使用 kaleido 輸出 png 的記憶體檔案"""113 img_bytes = pio.to_image(fig, format="png", engine="kaleido")114 return img_bytes115 116def create_composite_image(fig, student):117 """使用 PIL 合成圖片,確保學生的成績在最上層"""118 img_bytes = pio.to_image(fig, format="png", engine="kaleido")119 img = Image.open(BytesIO(img_bytes)).convert("RGBA")120 background = Image.new('RGBA', img.size, (255, 255, 255, 255))121 composite = Image.alpha_composite(background, img)122 return composite123 124def main():125 st.title('學生成績雷達圖產生器')126 127 uploaded_file = st.file_uploader("上傳CSV檔案", type=['csv'])128 129 if uploaded_file is not None:130 df = load_data(uploaded_file)131 132 if df is not None:133 numeric_columns = ['平均', '總分', '國文', '英文', '數學', '自科', '社會', '地理', '歷史', '公民']134 135 st.write("### 選擇要比較的欄位")136 selected_columns = [col for col in numeric_columns if st.checkbox(col, key=col)]137 138 st.write("### 選擇要比較的對象")139 selected_rows = st.multiselect('選擇要比較的對象', df['姓名'].tolist())140 141 if selected_columns and selected_rows:142 try:143 fig = create_radar_chart(df, selected_rows, selected_columns)144 st.plotly_chart(fig, use_container_width=True)145 except Exception as e:146 st.error(f"生成雷達圖時發生錯誤:{e}")147 148 st.write("### 批次繪製個別學生比較圖")149 150 individual_students = st.multiselect("選擇要個別比較的學生", df['姓名'].tolist(), key = "student")151 comparison_items = st.multiselect("選擇要比較的項目", df['姓名'].tolist(), key = "item")152 153 if individual_students and comparison_items:154 155 image_options = []156 image_bytes = {}157 for student in individual_students:158 fig = create_radar_chart(df, [student] + comparison_items, selected_columns)159 image_bytes[student] = create_composite_image(fig,student)160 image_options.append(f"{student} 與 {', '.join(comparison_items)} 的比較")161 162 selected_image_options = st.multiselect("選擇要顯示的圖片", options=image_options)163 164 cols = st.columns(3) # 排成三列165 for i, option in enumerate(selected_image_options):166 student = option.split(" 與 ")[0]167 with cols[i%3]:168 st.image(image_bytes[student], use_container_width=True)169 st.text(option)170 171if __name__ == "__main__":172 import numpy as np173 main()