s880453 commited on
Commit
fac038f
·
verified ·
1 Parent(s): 3269285

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +669 -241
app.py CHANGED
@@ -9,6 +9,9 @@ from PIL import Image
9
  import matplotlib.pyplot as plt
10
  import seaborn as sns
11
  from plotly.subplots import make_subplots
 
 
 
12
 
13
  # 擴展的圖表類型
14
  CHART_TYPES = [
@@ -39,6 +42,11 @@ COLOR_SCHEMES = {
39
  "綠色系": px.colors.sequential.Greens,
40
  "紫色系": px.colors.sequential.Purples,
41
  "灰度": px.colors.sequential.Greys,
 
 
 
 
 
42
  }
43
 
44
  # 圖案填充類型 (黑白印刷用)
@@ -48,9 +56,128 @@ PATTERN_TYPES = [
48
 
49
  # 統計函數選項
50
  AGGREGATION_FUNCTIONS = [
51
- "求和", "平均值", "最大值", "最小值", "計數", "中位數", "標準差", "變異數"
52
  ]
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  def agg_function_map(func_name):
55
  """映射中文統計函數名稱到Pandas函數"""
56
  mapping = {
@@ -63,11 +190,11 @@ def agg_function_map(func_name):
63
  "標準差": "std",
64
  "變異數": "var"
65
  }
66
- return mapping.get(func_name, "sum")
67
 
68
  def create_plot(df, chart_type, x_column, y_column, group_column=None, size_column=None,
69
  color_scheme="默認", patterns=[], title="", width=700, height=500,
70
- show_grid=True, show_legend=True, agg_function="求和", custom_colors={}):
71
  """創建圖表函數"""
72
 
73
  # 數據預處理
@@ -160,30 +287,37 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
160
  # 簡單計數
161
  counts = df[x_column].value_counts().reset_index()
162
  counts.columns = [x_column, 'count']
163
- fig = px.bar(counts, x=x_column, y='count', **fig_params)
164
 
165
  elif chart_type == "群組長條圖":
166
  if group_column and group_column in df.columns:
167
- grouped_df = df.pivot_table(index=x_column, columns=group_column,
168
- values=y_column, aggfunc=agg_func).reset_index()
169
- grouped_df = grouped_df.fillna(0)
170
 
171
- # 取得所有類別
172
- categories = grouped_df.columns.tolist()
 
 
 
 
 
 
 
173
  categories.remove(x_column)
174
 
175
  for i, category in enumerate(categories):
176
  color = colors[i % len(colors)]
177
- if category in custom_colors:
178
- color = custom_colors[category]
179
 
180
  pattern_shape = None
181
  if patterns and i < len(patterns) and patterns[i] != "無":
182
  pattern_shape = patterns[i]
183
 
184
  fig.add_trace(go.Bar(
185
- x=grouped_df[x_column],
186
- y=grouped_df[category],
187
  name=str(category),
188
  marker_color=color,
189
  marker_pattern_shape=pattern_shape
@@ -230,18 +364,25 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
230
 
231
  elif chart_type == "多重折線圖":
232
  if group_column and group_column in df.columns:
233
- grouped_df = df.pivot_table(index=x_column, columns=group_column,
234
- values=y_column, aggfunc=agg_func).reset_index()
235
- grouped_df = grouped_df.fillna(0)
236
 
237
- # 取得所有類別
238
- categories = grouped_df.columns.tolist()
 
 
 
 
 
 
 
239
  categories.remove(x_column)
240
 
241
  for i, category in enumerate(categories):
242
  color = colors[i % len(colors)]
243
- if category in custom_colors:
244
- color = custom_colors[category]
245
 
246
  line_dash = 'solid'
247
  if patterns and i < len(patterns) and patterns[i] != "無":
@@ -255,8 +396,8 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
255
  line_dash = 'longdash'
256
 
257
  fig.add_trace(go.Scatter(
258
- x=grouped_df[x_column],
259
- y=grouped_df[category],
260
  mode='lines+markers',
261
  name=str(category),
262
  line=dict(color=color, dash=line_dash),
@@ -268,18 +409,25 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
268
 
269
  elif chart_type == "階梯折線圖":
270
  if group_column and group_column in df.columns:
271
- grouped_df = df.pivot_table(index=x_column, columns=group_column,
272
- values=y_column, aggfunc=agg_func).reset_index()
273
- grouped_df = grouped_df.fillna(0)
274
 
275
- # 取得所有類別
276
- categories = grouped_df.columns.tolist()
 
 
 
 
 
 
 
277
  categories.remove(x_column)
278
 
279
  for i, category in enumerate(categories):
280
  color = colors[i % len(colors)]
281
- if category in custom_colors:
282
- color = custom_colors[category]
283
 
284
  line_dash = 'solid'
285
  if patterns and i < len(patterns) and patterns[i] != "無":
@@ -293,8 +441,8 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
293
  line_dash = 'longdash'
294
 
295
  fig.add_trace(go.Scatter(
296
- x=grouped_df[x_column],
297
- y=grouped_df[category],
298
  mode='lines',
299
  name=str(category),
300
  line=dict(shape='hv', color=color, dash=line_dash)
@@ -315,7 +463,7 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
315
  # 設置自定義顏色
316
  pie_colors = colors
317
  if custom_colors and len(custom_colors) > 0:
318
- pie_colors = [custom_colors.get(cat, colors[i % len(colors)])
319
  for i, cat in enumerate(grouped_df[x_column])]
320
 
321
  # 設置自定義圖案
@@ -330,13 +478,13 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
330
  fig = px.pie(grouped_df, names=x_column, values=y_column,
331
  color_discrete_sequence=pie_colors, **fig_params)
332
 
333
- # 應用圖案填充
334
  if pattern_shapes:
335
  for i, trace in enumerate(fig.data):
336
- for j, path in enumerate(trace.marker.pattern):
337
- if j < len(pattern_shapes) and pattern_shapes[j]:
338
- path.shape = pattern_shapes[j]
339
- path.solidity = 0.5
340
 
341
  elif chart_type == "環形圖":
342
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
@@ -344,7 +492,7 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
344
  # 設置自定義顏色
345
  pie_colors = colors
346
  if custom_colors and len(custom_colors) > 0:
347
- pie_colors = [custom_colors.get(cat, colors[i % len(colors)])
348
  for i, cat in enumerate(grouped_df[x_column])]
349
 
350
  fig = px.pie(grouped_df, names=x_column, values=y_column, hole=0.4,
@@ -353,10 +501,10 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
353
  # 應用圖案填充
354
  if patterns and len(patterns) > 0:
355
  for i, trace in enumerate(fig.data):
356
- trace.marker.pattern = {
357
- 'shape': patterns[i % len(patterns)] if patterns[i % len(patterns)] != "無" else None,
358
- 'solidity': 0.5
359
- }
360
 
361
  elif chart_type == "散點圖":
362
  if group_column and group_column in df.columns:
@@ -425,20 +573,26 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
425
 
426
  elif chart_type == "堆疊區域圖":
427
  if group_column and group_column in df.columns:
428
- # 創建樞紐表以便於繪製堆疊面積圖
429
- pivot_df = df.pivot_table(index=x_column, columns=group_column,
430
- values=y_column, aggfunc=agg_func).reset_index()
431
- pivot_df = pivot_df.fillna(0)
432
 
433
- # 獲取類別列
 
 
 
 
 
 
 
434
  categories = pivot_df.columns.tolist()
435
  categories.remove(x_column)
436
 
437
  # 建立堆疊區域圖
438
  for i, category in enumerate(categories):
439
  color = colors[i % len(colors)]
440
- if category in custom_colors:
441
- color = custom_colors[category]
442
 
443
  # 添加區域軌跡
444
  fig.add_trace(go.Scatter(
@@ -469,12 +623,16 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
469
  theta.append(theta[0])
470
  r.append(r[0])
471
 
 
 
 
 
472
  fig.add_trace(go.Scatterpolar(
473
  r=r,
474
  theta=theta,
475
  fill='toself',
476
  name=str(group),
477
- line_color=colors[i % len(colors)]
478
  ))
479
  else:
480
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
@@ -541,13 +699,19 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
541
  # 按值排序
542
  grouped_df = grouped_df.sort_values(by=y_column, ascending=False)
543
 
 
 
 
 
 
 
544
  # 創建漏斗圖
545
  fig = go.Figure(go.Funnel(
546
  y=grouped_df[x_column],
547
  x=grouped_df[y_column],
548
  textposition="inside",
549
  textinfo="value+percent initial",
550
- marker={"color": colors[:len(grouped_df)]}
551
  ))
552
 
553
  fig.update_layout(title=title)
@@ -570,9 +734,15 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
570
  elif chart_type == "極座標圖":
571
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
572
 
 
 
 
 
 
 
573
  # 創建極座標條形���
574
  fig = px.bar_polar(grouped_df, r=y_column, theta=x_column,
575
- color=x_column, color_discrete_sequence=colors, **fig_params)
576
 
577
  elif chart_type == "甘特圖":
578
  # 甘特圖需要開始和結束時間
@@ -624,7 +794,16 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
624
  height=height,
625
  showlegend=show_legend,
626
  xaxis=dict(showgrid=show_grid),
627
- yaxis=dict(showgrid=show_grid)
 
 
 
 
 
 
 
 
 
628
  )
629
 
630
  return fig
@@ -717,11 +896,11 @@ def update_columns(df):
717
  """更新列選擇下拉菜單"""
718
  if df is None or df.empty:
719
  # 默認列
720
- return gr.Dropdown(choices=["類別", "數值"], value="類別"), gr.Dropdown(choices=["類別", "數值"], value="數值"), gr.Dropdown(choices=["無", "類別", "數值"], value="無"), gr.Dropdown(choices=["無", "類別", "數值"], value="無")
721
 
722
  columns = df.columns.tolist()
723
  x_dropdown = gr.Dropdown(choices=columns, value=columns[0] if columns else None)
724
- y_dropdown = gr.Dropdown(choices=columns, value=columns[1] if len(columns) > 1 else columns[0])
725
  group_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
726
  size_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
727
 
@@ -763,10 +942,227 @@ def download_figure(fig, format_type="PNG"):
763
  except Exception as e:
764
  return None, f"導出圖表時出錯: {str(e)}"
765
 
766
- # 建立Gradio界面
767
- with gr.Blocks(title="進階數據可視化工具") as demo:
768
- gr.Markdown("# 進階數據可視化工具")
769
- gr.Markdown("上傳CSV或Excel文件,或直接在下方輸入數據來創建各種專業圖表")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
 
771
  # 狀態變量
772
  data_state = gr.State(None)
@@ -775,106 +1171,177 @@ with gr.Blocks(title="進階數據可視化工具") as demo:
775
 
776
  with gr.Tabs():
777
  # 數據輸入頁籤
778
- with gr.TabItem("數據輸入"):
779
- with gr.Row():
780
- with gr.Column():
781
- file_upload = gr.File(label="上傳CSV或Excel文件")
782
- upload_button = gr.Button("載入文件")
783
- upload_status = gr.Textbox(label="上傳狀態")
784
-
785
- with gr.Column():
786
- csv_input = gr.Textbox(label="或直接輸入數據(逗號或空格分隔)",
787
- placeholder="類別,數值\nA,10\nB,20\nC,15\nD,25\nE,30\n\n或\n\n類別 數值\nA 10\nB 20\nC 15\nD 25\nE 30",
788
- lines=10)
789
- parse_button = gr.Button("解析數據")
790
- parse_status = gr.Textbox(label="解析狀態")
791
-
792
- with gr.Row():
793
- data_preview = gr.Dataframe(label="數據預覽")
794
-
795
- with gr.Column():
796
- export_format = gr.Dropdown(["CSV", "Excel", "JSON"], label="導出格式", value="CSV")
797
- export_button = gr.Button("導出數據")
798
- export_result = gr.File(label="導出結果")
799
- export_status = gr.Textbox(label="導出狀態")
800
-
801
- # 圖表創建頁籤
802
- with gr.TabItem("圖表創建"):
803
  with gr.Row():
804
  with gr.Column(scale=1):
805
- chart_type = gr.Dropdown(
806
- CHART_TYPES,
807
- label="圖表類型",
808
- value="長條圖"
809
- )
810
-
811
- agg_function = gr.Dropdown(
812
- AGGREGATION_FUNCTIONS,
813
- label="聚合函數",
814
- value="計數",
815
- info="選擇如何彙總數據"
816
- )
817
 
818
- chart_title = gr.Textbox(label="圖表標題", placeholder="我的數據圖表")
 
 
 
819
 
820
- color_scheme = gr.Dropdown(
821
- list(COLOR_SCHEMES.keys()),
822
- label="顏色方案",
823
- value="默認"
824
- )
 
 
 
825
 
826
  with gr.Column(scale=1):
827
- # 軸和分組選擇
828
- x_column = gr.Dropdown(["類別"], label="X軸(或類別)")
829
- y_column = gr.Dropdown(["數值"], label="Y軸(或數值)")
830
- group_column = gr.Dropdown(["無"], label="分組列(用於多系列圖表)")
831
- size_column = gr.Dropdown(["無"], label="大小列(用於氣泡圖等)")
832
-
833
- # 尺寸控制
834
- chart_width = gr.Slider(300, 1200, 700, label="圖表寬度")
835
- chart_height = gr.Slider(300, 800, 500, label="圖表高度")
836
 
837
- # 顯示選項
838
- with gr.Row():
839
- show_grid = gr.Checkbox(label="顯示網格", value=True)
840
- show_legend = gr.Checkbox(label="顯示圖例", value=True)
841
-
842
- # 圖案和顏色自定義區
 
 
 
 
 
 
 
 
 
 
843
  with gr.Row():
844
- with gr.Column():
845
- gr.Markdown("### 圖案和顏色設置")
846
- gr.Markdown("為圖表元素設置特定的填充圖案(適用於黑白印刷)和顏色")
847
-
848
- # 動態添加圖案,先默認提供三個
849
- with gr.Row():
850
- pattern1 = gr.Dropdown(PATTERN_TYPES, label="圖案1", value="無")
851
- pattern2 = gr.Dropdown(PATTERN_TYPES, label="圖案2", value="無")
852
- pattern3 = gr.Dropdown(PATTERN_TYPES, label="圖案3", value="無")
853
 
854
- # 自定義顏色區域
855
- color_customization = gr.Textbox(
856
- label="自定義顏色 (格式: 類別1:#FF0000,類別2:#00FF00)",
857
- placeholder="A:#1f77b4,B:#ff7f0e,C:#2ca02c",
858
- info="輸入類別名稱和十六進制顏色代碼,用逗號分隔多個項目"
859
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
 
861
- with gr.Column():
862
- # 按鈕區
863
- update_button = gr.Button("更新圖表", variant="primary")
864
 
865
- with gr.Row():
866
- export_format = gr.Dropdown(
867
- ["PNG", "SVG", "PDF", "JPEG"],
868
- label="導出格式",
869
- value="PNG"
 
 
 
 
 
 
 
 
 
870
  )
871
- download_button = gr.Button("導出圖表")
 
 
872
 
873
- export_chart = gr.File(label="導出的圖表")
874
- export_chart_status = gr.Textbox(label="導出狀態")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
 
876
  # 圖表預覽區
877
- chart_output = gr.Plot(label="圖表預覽")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
 
879
  # 輔助函數
880
  def parse_custom_colors(color_text):
@@ -902,14 +1369,6 @@ with gr.Blocks(title="進階數據可視化工具") as demo:
902
  patterns.append(p3)
903
  return patterns
904
 
905
- def process_group_column(group_col):
906
- """處理分組列選擇"""
907
- return None if group_col == "無" else group_col
908
-
909
- def process_size_column(size_col):
910
- """處理大小列選擇"""
911
- return None if size_col == "無" else size_col
912
-
913
  # 事件處理
914
  upload_button.click(
915
  process_upload,
@@ -992,7 +1451,7 @@ with gr.Blocks(title="進階數據可視化工具") as demo:
992
  # 導出圖表
993
  download_button.click(
994
  download_figure,
995
- inputs=[chart_output, export_format],
996
  outputs=[export_chart, export_chart_status]
997
  )
998
 
@@ -1022,107 +1481,76 @@ with gr.Blocks(title="進階數據可視化工具") as demo:
1022
  update_element_visibility,
1023
  inputs=[chart_type],
1024
  outputs=[x_column, y_column, group_column, size_column]
1025
- )
1026
-
1027
- # 圖表相關自動更新函數
1028
- def auto_update_chart(df, chart_type, x_col, y_col, group_col, size_col, color_scheme, patterns_list, title, width, height, show_grid, show_legend, agg_func, custom_colors):
1029
- """圖表自動更新函數,用於各種輸入改變時"""
1030
- # 處理可能為"無"的選項
1031
- group_column_value = None if group_col == "無" else group_col
1032
- size_column_value = None if size_col == "無" else size_col
1033
-
1034
- return create_plot(
1035
- df, chart_type, x_col, y_col, group_column_value, size_column_value,
1036
- color_scheme, patterns_list, title, width, height, show_grid, show_legend, agg_func, custom_colors
1037
- )
1038
-
1039
- # 添加自動更新事件
1040
- chart_type.change(
1041
- auto_update_chart,
1042
  inputs=[
1043
- data_state, chart_type, x_column, y_column, group_column, size_column,
 
1044
  color_scheme, patterns_state, chart_title, chart_width, chart_height,
1045
  show_grid, show_legend, agg_function, custom_colors_state
1046
  ],
1047
  outputs=[chart_output]
1048
  )
1049
 
1050
- x_column.change(
1051
- auto_update_chart,
1052
- inputs=[
1053
- data_state, chart_type, x_column, y_column, group_column, size_column,
1054
- color_scheme, patterns_state, chart_title, chart_width, chart_height,
1055
- show_grid, show_legend, agg_function, custom_colors_state
1056
- ],
1057
- outputs=[chart_output]
1058
- )
1059
-
1060
- y_column.change(
1061
- auto_update_chart,
1062
- inputs=[
1063
- data_state, chart_type, x_column, y_column, group_column, size_column,
1064
- color_scheme, patterns_state, chart_title, chart_width, chart_height,
1065
- show_grid, show_legend, agg_function, custom_colors_state
1066
- ],
1067
- outputs=[chart_output]
1068
- )
1069
-
1070
- group_column.change(
1071
- auto_update_chart,
1072
- inputs=[
1073
- data_state, chart_type, x_column, y_column, group_column, size_column,
1074
- color_scheme, patterns_state, chart_title, chart_width, chart_height,
1075
- show_grid, show_legend, agg_function, custom_colors_state
1076
- ],
1077
- outputs=[chart_output]
1078
- )
1079
-
1080
- agg_function.change(
1081
- auto_update_chart,
1082
- inputs=[
1083
- data_state, chart_type, x_column, y_column, group_column, size_column,
1084
- color_scheme, patterns_state, chart_title, chart_width, chart_height,
1085
- show_grid, show_legend, agg_function, custom_colors_state
1086
- ],
1087
- outputs=[chart_output]
1088
- )
1089
-
1090
- color_scheme.change(
1091
- auto_update_chart,
1092
  inputs=[
1093
- data_state, chart_type, x_column, y_column, group_column, size_column,
1094
- color_scheme, patterns_state, chart_title, chart_width, chart_height,
 
1095
  show_grid, show_legend, agg_function, custom_colors_state
1096
  ],
1097
  outputs=[chart_output]
1098
  )
1099
-
1100
- # 在啟動應用之前添加使用說明
1101
- with demo:
1102
- gr.Markdown("""
1103
- ## 使用說明
1104
-
1105
- ### 數據輸入
1106
- - 上傳CSV或Excel文件,或在文本框中直接輸入數據
1107
- - 第一行被視為欄位名稱(表頭),不會納入統計
1108
- - 支持逗號分隔(CSV)或空格分隔的數據格式
1109
-
1110
- ### 圖表創建
1111
- - 選擇圖表類型:長條圖、折線圖、圓餅圖等多種專業圖表
1112
- - 聚合函數:選擇如何彙總數據(求和、平均值、最大值等)
1113
- - 分組列:用於創建多系列圖表,如按類別分組的長條圖
1114
- - 大小列:用於氣泡圖等需要額外數值控制大小的圖表
1115
 
1116
- ### 自定義選項
1117
- - 圖案填充:為圖表元素設置填充圖案,特別適用於黑白印刷
1118
- - 自定義顏色:為特定類別設置顏色,格式為"類別1:#FF0000,類別2:#00FF00"
1119
- - 導出格式:支持PNG、SVG、PDF和JPEG格式導出
1120
-
1121
- ### 注意事項
1122
- - 不同圖表類型需要不同的數據組織形式
1123
- - 圓餅圖和環形圖只需要類別和數值列
1124
- - 複雜圖表(如熱力圖、雷達圖)需要適當的數據結構
1125
- """)
 
 
 
 
 
 
 
 
 
1126
 
1127
  # 啟動應用
1128
  demo.launch()
 
9
  import matplotlib.pyplot as plt
10
  import seaborn as sns
11
  from plotly.subplots import make_subplots
12
+ import re
13
+ import json
14
+ import colorsys
15
 
16
  # 擴展的圖表類型
17
  CHART_TYPES = [
 
42
  "綠色系": px.colors.sequential.Greens,
43
  "紫色系": px.colors.sequential.Purples,
44
  "灰度": px.colors.sequential.Greys,
45
+ "彩虹": px.colors.sequential.Rainbow,
46
+ "漸變藍綠": px.colors.sequential.Turbo,
47
+ "漸變紫橙": px.colors.diverging.Spectral,
48
+ "漸變紅藍": px.colors.diverging.RdBu,
49
+ "漸變棕綠": px.colors.diverging.BrBG
50
  }
51
 
52
  # 圖案填充類型 (黑白印刷用)
 
56
 
57
  # 統計函數選項
58
  AGGREGATION_FUNCTIONS = [
59
+ "計數", "求和", "平均值", "最大值", "最小值", "中位數", "標準差", "變異數"
60
  ]
61
 
62
+ # HTML顏色展示卡片樣式
63
+ COLOR_CARD_STYLE = """
64
+ <div style="display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px;">
65
+ {color_cards}
66
+ </div>
67
+ """
68
+
69
+ COLOR_CARD_TEMPLATE = """
70
+ <div title="{color_name}" style="
71
+ width: 25px;
72
+ height: 25px;
73
+ background-color: {color_hex};
74
+ border-radius: 3px;
75
+ cursor: pointer;
76
+ border: 1px solid #ddd;
77
+ transition: transform 0.2s;
78
+ " onclick="copyToClipboard('{color_hex}')" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'"></div>
79
+ """
80
+
81
+ COPY_SCRIPT = """
82
+ <script>
83
+ function copyToClipboard(text) {
84
+ navigator.clipboard.writeText(text);
85
+ const notification = document.createElement('div');
86
+ notification.textContent = '已複製: ' + text;
87
+ notification.style.position = 'fixed';
88
+ notification.style.bottom = '20px';
89
+ notification.style.right = '20px';
90
+ notification.style.padding = '10px';
91
+ notification.style.background = '#333';
92
+ notification.style.color = 'white';
93
+ notification.style.borderRadius = '4px';
94
+ notification.style.zIndex = '1000';
95
+ document.body.appendChild(notification);
96
+ setTimeout(() => {
97
+ notification.style.opacity = '0';
98
+ notification.style.transition = 'opacity 0.5s';
99
+ setTimeout(() => document.body.removeChild(notification), 500);
100
+ }, 1500);
101
+ }
102
+ </script>
103
+ """
104
+
105
+ # 常見的顏色名稱和十六進制代碼
106
+ COMMON_COLORS = {
107
+ "紅色": "#FF0000", "橙色": "#FFA500", "黃色": "#FFFF00", "綠色": "#008000",
108
+ "藍色": "#0000FF", "紫色": "#800080", "粉紅色": "#FFC0CB", "棕色": "#A52A2A",
109
+ "灰色": "#808080", "黑色": "#000000", "白色": "#FFFFFF", "青色": "#00FFFF",
110
+ "洋紅": "#FF00FF", "淺藍": "#ADD8E6", "淺綠": "#90EE90", "淺黃": "#FFFFE0"
111
+ }
112
+
113
+ # 生成漸變色系
114
+ def generate_gradient_colors(start_color, end_color, steps=10):
115
+ def hex_to_rgb(hex_color):
116
+ hex_color = hex_color.lstrip('#')
117
+ return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
118
+
119
+ def rgb_to_hex(rgb):
120
+ return '#{:02x}{:02x}{:02x}'.format(int(rgb[0]), int(rgb[1]), int(rgb[2]))
121
+
122
+ start_rgb = hex_to_rgb(start_color)
123
+ end_rgb = hex_to_rgb(end_color)
124
+
125
+ r_step = (end_rgb[0] - start_rgb[0]) / (steps - 1)
126
+ g_step = (end_rgb[1] - start_rgb[1]) / (steps - 1)
127
+ b_step = (end_rgb[2] - start_rgb[2]) / (steps - 1)
128
+
129
+ gradient_colors = []
130
+ for i in range(steps):
131
+ r = start_rgb[0] + r_step * i
132
+ g = start_rgb[1] + g_step * i
133
+ b = start_rgb[2] + b_step * i
134
+ gradient_colors.append(rgb_to_hex((r, g, b)))
135
+
136
+ return gradient_colors
137
+
138
+ # 為顏色選擇添加的各種漸變色系
139
+ GRADIENTS = {
140
+ "紅→黃": generate_gradient_colors("#FF0000", "#FFFF00"),
141
+ "藍→綠": generate_gradient_colors("#0000FF", "#00FF00"),
142
+ "紫→粉": generate_gradient_colors("#800080", "#FFC0CB"),
143
+ "紅→藍": generate_gradient_colors("#FF0000", "#0000FF"),
144
+ "黑→白": generate_gradient_colors("#000000", "#FFFFFF"),
145
+ "彩虹": ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"]
146
+ }
147
+
148
+ # 生成顏色卡片展示
149
+ def generate_color_cards():
150
+ # 常見顏色卡片
151
+ common_cards = ""
152
+ for name, hex_code in COMMON_COLORS.items():
153
+ common_cards += COLOR_CARD_TEMPLATE.format(color_name=name, color_hex=hex_code)
154
+
155
+ # 漸變色系卡片
156
+ gradient_cards = {}
157
+ for name, colors in GRADIENTS.items():
158
+ gradient_cards[name] = ""
159
+ for i, color in enumerate(colors):
160
+ gradient_cards[name] += COLOR_CARD_TEMPLATE.format(
161
+ color_name=f"{name} {i+1}/{len(colors)}",
162
+ color_hex=color
163
+ )
164
+
165
+ # 合成卡片展示HTML
166
+ color_display = f"""
167
+ <div style="font-weight: bold; margin-top: 10px;">常見顏色</div>
168
+ {COLOR_CARD_STYLE.format(color_cards=common_cards)}
169
+ """
170
+
171
+ for name, cards in gradient_cards.items():
172
+ color_display += f"""
173
+ <div style="font-weight: bold; margin-top: 10px;">{name}</div>
174
+ {COLOR_CARD_STYLE.format(color_cards=cards)}
175
+ """
176
+
177
+ color_display += COPY_SCRIPT
178
+
179
+ return color_display
180
+
181
  def agg_function_map(func_name):
182
  """映射中文統計函數名稱到Pandas函數"""
183
  mapping = {
 
190
  "標準差": "std",
191
  "變異數": "var"
192
  }
193
+ return mapping.get(func_name, "count")
194
 
195
  def create_plot(df, chart_type, x_column, y_column, group_column=None, size_column=None,
196
  color_scheme="默認", patterns=[], title="", width=700, height=500,
197
+ show_grid=True, show_legend=True, agg_function="計數", custom_colors={}):
198
  """創建圖表函數"""
199
 
200
  # 數據預處理
 
287
  # 簡單計數
288
  counts = df[x_column].value_counts().reset_index()
289
  counts.columns = [x_column, 'count']
290
+ fig = px.bar(counts, x=x_column, y='count', **fig_params)
291
 
292
  elif chart_type == "群組長條圖":
293
  if group_column and group_column in df.columns:
294
+ # 明確將字符串列轉換為類別型
295
+ df[x_column] = df[x_column].astype('category')
296
+ df[group_column] = df[group_column].astype('category')
297
 
298
+ # 先進行分組統計
299
+ group_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
300
+
301
+ # 創建樞紐表
302
+ pivot_df = group_df.pivot_table(index=x_column, columns=group_column,
303
+ values=y_column).reset_index().fillna(0)
304
+
305
+ # 獲取所有類別
306
+ categories = pivot_df.columns.tolist()
307
  categories.remove(x_column)
308
 
309
  for i, category in enumerate(categories):
310
  color = colors[i % len(colors)]
311
+ if str(category) in custom_colors:
312
+ color = custom_colors[str(category)]
313
 
314
  pattern_shape = None
315
  if patterns and i < len(patterns) and patterns[i] != "無":
316
  pattern_shape = patterns[i]
317
 
318
  fig.add_trace(go.Bar(
319
+ x=pivot_df[x_column],
320
+ y=pivot_df[category],
321
  name=str(category),
322
  marker_color=color,
323
  marker_pattern_shape=pattern_shape
 
364
 
365
  elif chart_type == "多重折線圖":
366
  if group_column and group_column in df.columns:
367
+ # 明確將字符串列轉換為類別型
368
+ df[x_column] = df[x_column].astype('category')
369
+ df[group_column] = df[group_column].astype('category')
370
 
371
+ # 先進行分組統計
372
+ group_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
373
+
374
+ # 創建樞紐表
375
+ pivot_df = group_df.pivot_table(index=x_column, columns=group_column,
376
+ values=y_column).reset_index().fillna(0)
377
+
378
+ # 獲取所有類別
379
+ categories = pivot_df.columns.tolist()
380
  categories.remove(x_column)
381
 
382
  for i, category in enumerate(categories):
383
  color = colors[i % len(colors)]
384
+ if str(category) in custom_colors:
385
+ color = custom_colors[str(category)]
386
 
387
  line_dash = 'solid'
388
  if patterns and i < len(patterns) and patterns[i] != "無":
 
396
  line_dash = 'longdash'
397
 
398
  fig.add_trace(go.Scatter(
399
+ x=pivot_df[x_column],
400
+ y=pivot_df[category],
401
  mode='lines+markers',
402
  name=str(category),
403
  line=dict(color=color, dash=line_dash),
 
409
 
410
  elif chart_type == "階梯折線圖":
411
  if group_column and group_column in df.columns:
412
+ # 明確將字符串列轉換為類別型
413
+ df[x_column] = df[x_column].astype('category')
414
+ df[group_column] = df[group_column].astype('category')
415
 
416
+ # 先進行分組統計
417
+ group_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
418
+
419
+ # 創建樞紐表
420
+ pivot_df = group_df.pivot_table(index=x_column, columns=group_column,
421
+ values=y_column).reset_index().fillna(0)
422
+
423
+ # 獲取所有類別
424
+ categories = pivot_df.columns.tolist()
425
  categories.remove(x_column)
426
 
427
  for i, category in enumerate(categories):
428
  color = colors[i % len(colors)]
429
+ if str(category) in custom_colors:
430
+ color = custom_colors[str(category)]
431
 
432
  line_dash = 'solid'
433
  if patterns and i < len(patterns) and patterns[i] != "無":
 
441
  line_dash = 'longdash'
442
 
443
  fig.add_trace(go.Scatter(
444
+ x=pivot_df[x_column],
445
+ y=pivot_df[category],
446
  mode='lines',
447
  name=str(category),
448
  line=dict(shape='hv', color=color, dash=line_dash)
 
463
  # 設置自定義顏色
464
  pie_colors = colors
465
  if custom_colors and len(custom_colors) > 0:
466
+ pie_colors = [custom_colors.get(str(cat), colors[i % len(colors)])
467
  for i, cat in enumerate(grouped_df[x_column])]
468
 
469
  # 設置自定義圖案
 
478
  fig = px.pie(grouped_df, names=x_column, values=y_column,
479
  color_discrete_sequence=pie_colors, **fig_params)
480
 
481
+ # 應用圖案填充 (Plotly可能不支持直接在餅圖上應用花紋,此處為嘗試)
482
  if pattern_shapes:
483
  for i, trace in enumerate(fig.data):
484
+ trace.marker.pattern = dict(
485
+ shape=pattern_shapes[i % len(pattern_shapes)] if i < len(pattern_shapes) else None,
486
+ solidity=0.5
487
+ )
488
 
489
  elif chart_type == "環形圖":
490
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
 
492
  # 設置自定義顏色
493
  pie_colors = colors
494
  if custom_colors and len(custom_colors) > 0:
495
+ pie_colors = [custom_colors.get(str(cat), colors[i % len(colors)])
496
  for i, cat in enumerate(grouped_df[x_column])]
497
 
498
  fig = px.pie(grouped_df, names=x_column, values=y_column, hole=0.4,
 
501
  # 應用圖案填充
502
  if patterns and len(patterns) > 0:
503
  for i, trace in enumerate(fig.data):
504
+ trace.marker.pattern = dict(
505
+ shape=patterns[i % len(patterns)] if patterns[i % len(patterns)] != "無" else None,
506
+ solidity=0.5
507
+ )
508
 
509
  elif chart_type == "散點圖":
510
  if group_column and group_column in df.columns:
 
573
 
574
  elif chart_type == "堆疊區域圖":
575
  if group_column and group_column in df.columns:
576
+ # 明確將字符串列轉換為類別型
577
+ df[x_column] = df[x_column].astype('category')
578
+ df[group_column] = df[group_column].astype('category')
 
579
 
580
+ # 先進行分組統計
581
+ group_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
582
+
583
+ # 創建樞紐表
584
+ pivot_df = group_df.pivot_table(index=x_column, columns=group_column,
585
+ values=y_column).reset_index().fillna(0)
586
+
587
+ # 獲取所有類別
588
  categories = pivot_df.columns.tolist()
589
  categories.remove(x_column)
590
 
591
  # 建立堆疊區域圖
592
  for i, category in enumerate(categories):
593
  color = colors[i % len(colors)]
594
+ if str(category) in custom_colors:
595
+ color = custom_colors[str(category)]
596
 
597
  # 添加區域軌跡
598
  fig.add_trace(go.Scatter(
 
623
  theta.append(theta[0])
624
  r.append(r[0])
625
 
626
+ color = colors[i % len(colors)]
627
+ if str(group) in custom_colors:
628
+ color = custom_colors[str(group)]
629
+
630
  fig.add_trace(go.Scatterpolar(
631
  r=r,
632
  theta=theta,
633
  fill='toself',
634
  name=str(group),
635
+ line_color=color
636
  ))
637
  else:
638
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
 
699
  # 按值排序
700
  grouped_df = grouped_df.sort_values(by=y_column, ascending=False)
701
 
702
+ # 設置自定義顏色
703
+ funnel_colors = colors[:len(grouped_df)]
704
+ if custom_colors and len(custom_colors) > 0:
705
+ funnel_colors = [custom_colors.get(str(cat), colors[i % len(colors)])
706
+ for i, cat in enumerate(grouped_df[x_column])]
707
+
708
  # 創建漏斗圖
709
  fig = go.Figure(go.Funnel(
710
  y=grouped_df[x_column],
711
  x=grouped_df[y_column],
712
  textposition="inside",
713
  textinfo="value+percent initial",
714
+ marker={"color": funnel_colors}
715
  ))
716
 
717
  fig.update_layout(title=title)
 
734
  elif chart_type == "極座標圖":
735
  grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
736
 
737
+ # 設置自定義顏色
738
+ polar_colors = colors[:len(grouped_df)]
739
+ if custom_colors and len(custom_colors) > 0:
740
+ polar_colors = [custom_colors.get(str(cat), colors[i % len(colors)])
741
+ for i, cat in enumerate(grouped_df[x_column])]
742
+
743
  # 創建極座標條形���
744
  fig = px.bar_polar(grouped_df, r=y_column, theta=x_column,
745
+ color=x_column, color_discrete_sequence=polar_colors, **fig_params)
746
 
747
  elif chart_type == "甘特圖":
748
  # 甘特圖需要開始和結束時間
 
794
  height=height,
795
  showlegend=show_legend,
796
  xaxis=dict(showgrid=show_grid),
797
+ yaxis=dict(showgrid=show_grid),
798
+ # 現代化樣式設置
799
+ template="plotly_white",
800
+ margin=dict(l=50, r=50, t=50, b=50),
801
+ font=dict(family="Arial, sans-serif"),
802
+ hoverlabel=dict(
803
+ bgcolor="white",
804
+ font_size=12,
805
+ font_family="Arial, sans-serif"
806
+ )
807
  )
808
 
809
  return fig
 
896
  """更新列選擇下拉菜單"""
897
  if df is None or df.empty:
898
  # 默認列
899
+ return gr.Dropdown(choices=["類別", "數值", "計數"], value="類別"), gr.Dropdown(choices=["類別", "數值", "計數"], value="計數"), gr.Dropdown(choices=["無", "類別", "數值", "計數"], value="無"), gr.Dropdown(choices=["無", "類別", "數值", "計數"], value="無")
900
 
901
  columns = df.columns.tolist()
902
  x_dropdown = gr.Dropdown(choices=columns, value=columns[0] if columns else None)
903
+ y_dropdown = gr.Dropdown(choices=columns, value="計數" if "計數" in columns else (columns[1] if len(columns) > 1 else columns[0]))
904
  group_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
905
  size_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
906
 
 
942
  except Exception as e:
943
  return None, f"導出圖表時出錯: {str(e)}"
944
 
945
+ def recommend_chart_settings(df):
946
+ """智能分析數據並推薦最佳圖表設置"""
947
+ if df is None or df.empty:
948
+ return {
949
+ "message": "請先上傳或輸入數據"
950
+ }
951
+
952
+ # 獲取列名和數據類型
953
+ columns = df.columns.tolist()
954
+ num_columns = df.select_dtypes(include=['number']).columns.tolist()
955
+ cat_columns = df.select_dtypes(include=['object', 'category']).columns.tolist()
956
+
957
+ # 排除"計數"列
958
+ if "計數" in num_columns:
959
+ num_columns.remove("計數")
960
+
961
+ # 統計數據特徵
962
+ num_rows = len(df)
963
+ num_unique_values = {col: df[col].nunique() for col in columns}
964
+
965
+ # 初始化推薦結果
966
+ recommendation = {
967
+ "chart_type": None,
968
+ "x_column": None,
969
+ "y_column": None,
970
+ "group_column": None,
971
+ "agg_function": None,
972
+ "message": ""
973
+ }
974
+
975
+ # 根據數據特徵推薦圖表
976
+ # 案例1: 有2個類別列,需要分析它們之間的關係
977
+ if len(cat_columns) >= 2 and '計數' in columns:
978
+ # 例如機構類型和情緒分析數據
979
+ recommendation["chart_type"] = "堆疊長條圖"
980
+ recommendation["x_column"] = cat_columns[0] # 第一個類別列作為X軸
981
+ recommendation["y_column"] = "計數" # 使用計數列作為Y軸
982
+ recommendation["group_column"] = cat_columns[1] # 第二個類別列作為分組
983
+ recommendation["agg_function"] = "求和"
984
+ recommendation["message"] = f"檢測到有類別列 '{cat_columns[0]}' 和 '{cat_columns[1]}',推薦使用堆疊長條圖來顯示兩者關係"
985
+
986
+ # 案例2: 只有一個類別列
987
+ elif len(cat_columns) == 1 and '計數' in columns:
988
+ recommendation["chart_type"] = "長條圖"
989
+ recommendation["x_column"] = cat_columns[0]
990
+ recommendation["y_column"] = "計數"
991
+ recommendation["agg_function"] = "求和"
992
+ recommendation["message"] = f"檢測到類別列 '{cat_columns[0]}',推薦使用長條圖來顯示分佈"
993
+
994
+ # 案例3: 有時間序列數據
995
+ elif any("日期" in col or "時間" in col for col in columns) and len(num_columns) > 0:
996
+ date_col = next((col for col in columns if "日期" in col or "時間" in col), None)
997
+ recommendation["chart_type"] = "折線圖"
998
+ recommendation["x_column"] = date_col
999
+ recommendation["y_column"] = num_columns[0] if num_columns else "計數"
1000
+ recommendation["agg_function"] = "平均值"
1001
+ recommendation["message"] = f"檢測到時間列 '{date_col}',推薦使用折線圖來顯示趨勢"
1002
+
1003
+ # 案例4: 有多個數值列
1004
+ elif len(num_columns) >= 2:
1005
+ recommendation["chart_type"] = "散點圖"
1006
+ recommendation["x_column"] = num_columns[0]
1007
+ recommendation["y_column"] = num_columns[1]
1008
+ recommendation["message"] = f"檢測到數值列 '{num_columns[0]}' 和 '{num_columns[1]}',推薦使用散點圖來分析相關性"
1009
+
1010
+ # 預設情況
1011
+ else:
1012
+ if len(cat_columns) > 0 and "計數" in columns:
1013
+ recommendation["chart_type"] = "長條圖"
1014
+ recommendation["x_column"] = cat_columns[0]
1015
+ recommendation["y_column"] = "計數"
1016
+ recommendation["agg_function"] = "求和"
1017
+ recommendation["message"] = "根據數據結構,推薦使用長條圖"
1018
+ elif len(cat_columns) > 0 and len(num_columns) > 0:
1019
+ recommendation["chart_type"] = "長條圖"
1020
+ recommendation["x_column"] = cat_columns[0]
1021
+ recommendation["y_column"] = num_columns[0]
1022
+ recommendation["agg_function"] = "平均值"
1023
+ recommendation["message"] = "根據數據結構,推薦使用長條圖"
1024
+ else:
1025
+ recommendation["message"] = "無法確定最佳圖表類型,請手動選擇"
1026
+
1027
+ return recommendation
1028
+
1029
+ # CSS樣式
1030
+ CUSTOM_CSS = """
1031
+ .gradio-container {
1032
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1033
+ background: linear-gradient(to bottom right, #f7f9fc, #e9f0f8);
1034
+ }
1035
+
1036
+ .app-header {
1037
+ text-align: center;
1038
+ margin-bottom: 20px;
1039
+ background: linear-gradient(90deg, #4568dc, #3a6073);
1040
+ padding: 20px;
1041
+ border-radius: 10px;
1042
+ color: white;
1043
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1044
+ }
1045
+
1046
+ .app-title {
1047
+ font-size: 32px;
1048
+ font-weight: bold;
1049
+ margin: 0;
1050
+ display: inline-block;
1051
+ background: linear-gradient(to right, #ffffff, #e0e0e0);
1052
+ -webkit-background-clip: text;
1053
+ -webkit-text-fill-color: transparent;
1054
+ }
1055
+
1056
+ .app-subtitle {
1057
+ font-size: 16px;
1058
+ color: #f0f0f0;
1059
+ margin-top: 5px;
1060
+ }
1061
+
1062
+ .section-title {
1063
+ font-size: 20px;
1064
+ font-weight: bold;
1065
+ color: #333;
1066
+ border-bottom: 2px solid #4568dc;
1067
+ padding-bottom: 5px;
1068
+ margin-top: 20px;
1069
+ margin-bottom: 15px;
1070
+ }
1071
+
1072
+ .card {
1073
+ background-color: white;
1074
+ border-radius: 10px;
1075
+ padding: 20px;
1076
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1077
+ margin-bottom: 20px;
1078
+ transition: transform 0.3s, box-shadow 0.3s;
1079
+ }
1080
+
1081
+ .card:hover {
1082
+ transform: translateY(-5px);
1083
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
1084
+ }
1085
+
1086
+ .primary-button {
1087
+ background: linear-gradient(90deg, #4568dc, #3a6073) !important;
1088
+ border: none !important;
1089
+ color: white !important;
1090
+ font-weight: bold !important;
1091
+ padding: 10px 20px !important;
1092
+ border-radius: 5px !important;
1093
+ cursor: pointer !important;
1094
+ transition: all 0.3s ease !important;
1095
+ }
1096
+
1097
+ .primary-button:hover {
1098
+ background: linear-gradient(90deg, #3a6073, #4568dc) !important;
1099
+ transform: translateY(-2px) !important;
1100
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1) !important;
1101
+ }
1102
+
1103
+ .secondary-button {
1104
+ background: linear-gradient(90deg, #6a85b6, #bac8e0) !important;
1105
+ border: none !important;
1106
+ color: white !important;
1107
+ font-weight: bold !important;
1108
+ padding: 10px 20px !important;
1109
+ border-radius: 5px !important;
1110
+ cursor: pointer !important;
1111
+ transition: all 0.3s ease !important;
1112
+ }
1113
+
1114
+ .secondary-button:hover {
1115
+ background: linear-gradient(90deg, #bac8e0, #6a85b6) !important;
1116
+ transform: translateY(-2px) !important;
1117
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1) !important;
1118
+ }
1119
+
1120
+ .color-panel {
1121
+ background-color: white;
1122
+ border-radius: 5px;
1123
+ padding: 10px;
1124
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
1125
+ }
1126
+
1127
+ .tips-box {
1128
+ background-color: #f1f7fe;
1129
+ border-left: 4px solid #4568dc;
1130
+ padding: 15px;
1131
+ border-radius: 5px;
1132
+ margin: 20px 0;
1133
+ }
1134
+
1135
+ .chart-previewer {
1136
+ border: 2px dashed #ccc;
1137
+ border-radius: 10px;
1138
+ padding: 20px;
1139
+ min-height: 400px;
1140
+ display: flex;
1141
+ justify-content: center;
1142
+ align-items: center;
1143
+ background-color: rgba(255, 255, 255, 0.7);
1144
+ }
1145
+
1146
+ /* Loading animation */
1147
+ @keyframes pulse {
1148
+ 0% { opacity: 0.6; }
1149
+ 50% { opacity: 1; }
1150
+ 100% { opacity: 0.6; }
1151
+ }
1152
+
1153
+ .loading {
1154
+ animation: pulse 1.5s infinite;
1155
+ }
1156
+ """
1157
+
1158
+ # 現代化UI界面
1159
+ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具") as demo:
1160
+ gr.HTML("""
1161
+ <div class="app-header">
1162
+ <h1 class="app-title">🎨 進階數據可視化工具</h1>
1163
+ <p class="app-subtitle">上傳數據,創建各種專業圖表,輕鬆實現數據可視化</p>
1164
+ </div>
1165
+ """)
1166
 
1167
  # 狀態變量
1168
  data_state = gr.State(None)
 
1171
 
1172
  with gr.Tabs():
1173
  # 數據輸入頁籤
1174
+ with gr.TabItem("📊 數據輸入") as tab_data:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1175
  with gr.Row():
1176
  with gr.Column(scale=1):
1177
+ gr.HTML('<div class="section-title">上傳或輸入數據</div>')
 
 
 
 
 
 
 
 
 
 
 
1178
 
1179
+ with gr.Box(elem_classes=["card"]):
1180
+ file_upload = gr.File(label="上傳CSV或Excel文件")
1181
+ upload_button = gr.Button("載入文件", elem_classes=["primary-button"])
1182
+ upload_status = gr.Textbox(label="上傳狀態", lines=2)
1183
 
1184
+ with gr.Box(elem_classes=["card"]):
1185
+ csv_input = gr.Textbox(
1186
+ label="直接輸入數據(逗號或空格分隔)",
1187
+ placeholder="類別,數值\nA,10\nB,20\nC,15\nD,25\nE,30\n\n或\n\n類別 數值\nA 10\nB 20\nC 15\nD 25\nE 30",
1188
+ lines=10
1189
+ )
1190
+ parse_button = gr.Button("解析數據", elem_classes=["primary-button"])
1191
+ parse_status = gr.Textbox(label="解析狀態", lines=2)
1192
 
1193
  with gr.Column(scale=1):
1194
+ gr.HTML('<div class="section-title">數據預覽</div>')
 
 
 
 
 
 
 
 
1195
 
1196
+ with gr.Box(elem_classes=["card"]):
1197
+ data_preview = gr.Dataframe(label="數據表格預覽", interactive=False)
1198
+
1199
+ with gr.Row():
1200
+ export_format = gr.Dropdown(
1201
+ ["CSV", "Excel", "JSON"],
1202
+ label="導出格式",
1203
+ value="CSV"
1204
+ )
1205
+ export_button = gr.Button("導出數據", elem_classes=["secondary-button"])
1206
+
1207
+ export_result = gr.File(label="導出結果")
1208
+ export_status = gr.Textbox(label="導出狀態", lines=2)
1209
+
1210
+ # 圖表創建頁籤
1211
+ with gr.TabItem("📈 圖表創建") as tab_chart:
1212
  with gr.Row():
1213
+ with gr.Column(scale=1):
1214
+ gr.HTML('<div class="section-title">圖表設置</div>')
 
 
 
 
 
 
 
1215
 
1216
+ with gr.Box(elem_classes=["card"]):
1217
+ chart_type = gr.Dropdown(
1218
+ CHART_TYPES,
1219
+ label="📊 圖表類型",
1220
+ value="長條圖",
1221
+ interactive=True
1222
+ )
1223
+
1224
+ with gr.Row():
1225
+ recommend_button = gr.Button("🧠 智能推薦圖表", variant="secondary", elem_classes=["secondary-button"])
1226
+ recommendation_result = gr.Textbox(label="推薦結果", lines=2)
1227
+
1228
+ chart_title = gr.Textbox(label="📝 圖表標題", placeholder="我的數據圖表")
1229
+
1230
+ agg_function = gr.Dropdown(
1231
+ AGGREGATION_FUNCTIONS,
1232
+ label="🔄 聚合函數",
1233
+ value="計數",
1234
+ info="選擇如何彙總數據"
1235
+ )
1236
+
1237
+ gr.HTML("<div class='section-title'>數據映射</div>")
1238
+
1239
+ # 軸和分組選擇
1240
+ x_column = gr.Dropdown(["類別"], label="X軸(或類別)")
1241
+ y_column = gr.Dropdown(["計數"], label="Y軸(或數值)")
1242
+ group_column = gr.Dropdown(["無"], label="分組列(用於多系列圖表)")
1243
+ size_column = gr.Dropdown(["無"], label="大小列(用於氣泡圖等)")
1244
+
1245
+ gr.HTML("<div class='tips-box'>💡 提示: 選擇不同圖表類型時,界面會自動調整顯示相關設置選項</div>")
1246
 
1247
+ with gr.Column(scale=1):
1248
+ gr.HTML('<div class="section-title">顯示選項</div>')
 
1249
 
1250
+ with gr.Box(elem_classes=["card"]):
1251
+ # 尺寸控制
1252
+ with gr.Row():
1253
+ chart_width = gr.Slider(300, 1200, 800, label="圖表寬度")
1254
+ chart_height = gr.Slider(300, 800, 500, label="圖表高度")
1255
+
1256
+ with gr.Row():
1257
+ show_grid = gr.Checkbox(label="顯示網格", value=True)
1258
+ show_legend = gr.Checkbox(label="顯示圖例", value=True)
1259
+
1260
+ color_scheme = gr.Dropdown(
1261
+ list(COLOR_SCHEMES.keys()),
1262
+ label="🎨 顏色方案",
1263
+ value="默認"
1264
  )
1265
+
1266
+ gr.HTML('<div style="margin-top: 10px;"><b>顏色參考</b> (點擊顏色可複製顏色代碼)</div>')
1267
+ gr.HTML(generate_color_cards(), elem_id="color_display")
1268
 
1269
+ # 圖案和顏色自定義區
1270
+ with gr.Box(elem_classes=["card"]):
1271
+ gr.HTML('<div class="section-title">自定義圖案和顏色</div>')
1272
+
1273
+ # 動態添加圖案,先默認提供三個
1274
+ with gr.Row():
1275
+ pattern1 = gr.Dropdown(PATTERN_TYPES, label="圖案1", value="無")
1276
+ pattern2 = gr.Dropdown(PATTERN_TYPES, label="圖案2", value="無")
1277
+ pattern3 = gr.Dropdown(PATTERN_TYPES, label="圖案3", value="無")
1278
+
1279
+ # 自定義顏色區域
1280
+ color_customization = gr.Textbox(
1281
+ label="自定義顏色 (格式: 類別1:#FF0000,類別2:#00FF00)",
1282
+ placeholder="正面:#2ca02c,負面:#ff7f0e,中性:#1f77b4",
1283
+ info="輸入類別名稱和十六進制顏色代碼,用逗號分隔多個項目"
1284
+ )
1285
+
1286
+ with gr.Row():
1287
+ update_button = gr.Button("更新圖表", variant="primary", elem_classes=["primary-button"])
1288
+
1289
+ with gr.Row():
1290
+ export_img_format = gr.Dropdown(
1291
+ ["PNG", "SVG", "PDF", "JPEG"],
1292
+ label="導出格式",
1293
+ value="PNG"
1294
+ )
1295
+ download_button = gr.Button("導出圖表", elem_classes=["secondary-button"])
1296
+
1297
+ export_chart = gr.File(label="導出的圖表")
1298
+ export_chart_status = gr.Textbox(label="導出狀態", lines=2)
1299
 
1300
  # 圖表預覽區
1301
+ gr.HTML('<div class="section-title">圖表預覽</div>')
1302
+ with gr.Box(elem_classes=["chart-previewer"]):
1303
+ chart_output = gr.Plot(label="", elem_id="chart_preview")
1304
+
1305
+ # 使用說明頁籤
1306
+ with gr.TabItem("📖 使用說明") as tab_help:
1307
+ gr.HTML("""
1308
+ <div class="card">
1309
+ <div class="section-title">使用說明</div>
1310
+
1311
+ <h3>數據輸入</h3>
1312
+ <ul>
1313
+ <li>上傳CSV或Excel文件,或在文本框中直接輸入數據</li>
1314
+ <li>第一行被視為欄位名稱(表頭),不會納入統計</li>
1315
+ <li>支持逗號分隔(CSV)或空格分隔的數據格式</li>
1316
+ <li>系統會自動添加「計數」列,方便進行計數統計</li>
1317
+ </ul>
1318
+
1319
+ <h3>圖表創建</h3>
1320
+ <ul>
1321
+ <li><strong>智能推薦:</strong>系統可根據您的數據結構智能推薦最適合的圖表類型和設置</li>
1322
+ <li><strong>圖表類型:</strong>支持20多種專業圖表,包括長條圖、堆疊長條圖、折線圖、圓餅圖等</li>
1323
+ <li><strong>聚合函數:</strong>選擇如何彙總數據(計數、求和、平均值、最大值等)</li>
1324
+ <li><strong>分組列:</strong>用於創建多系列圖表,例如按類別分組的長條圖</li>
1325
+ <li><strong>大小列:</strong>用於氣泡圖等需要額外數值控制大小的圖表</li>
1326
+ </ul>
1327
+
1328
+ <h3>自定義選項</h3>
1329
+ <ul>
1330
+ <li><strong>顏色方案:</strong>選擇預設的顏色系列,包括明亮、柔和、漸變等多種風格</li>
1331
+ <li><strong>自定義顏色:</strong>為特定類別設置顏色,格式為"類別1:#FF0000,類別2:#00FF00"</li>
1332
+ <li><strong>圖案填充:</strong>為圖表元素設置填充圖案,特別適用於黑白印刷</li>
1333
+ <li><strong>導出格式:</strong>支持PNG、SVG、PDF和JPEG格式導出</li>
1334
+ </ul>
1335
+
1336
+ <h3>常見使用場景</h3>
1337
+ <ul>
1338
+ <li><strong>分類數據分析:</strong>使用長條圖或圓餅圖展示不同類別的分布</li>
1339
+ <li><strong>多分類比較:</strong>使用堆疊長條圖或群組長條圖展示多個分類維度的關係</li>
1340
+ <li><strong>趨勢分析:</strong>使用折線圖或區域圖展示數據隨時間的變化</li>
1341
+ <li><strong>相關性分析:</strong>使用散點圖或熱力圖分析變量之間的關係</li>
1342
+ </ul>
1343
+ </div>
1344
+ """)
1345
 
1346
  # 輔助函數
1347
  def parse_custom_colors(color_text):
 
1369
  patterns.append(p3)
1370
  return patterns
1371
 
 
 
 
 
 
 
 
 
1372
  # 事件處理
1373
  upload_button.click(
1374
  process_upload,
 
1451
  # 導出圖表
1452
  download_button.click(
1453
  download_figure,
1454
+ inputs=[chart_output, export_img_format],
1455
  outputs=[export_chart, export_chart_status]
1456
  )
1457
 
 
1481
  update_element_visibility,
1482
  inputs=[chart_type],
1483
  outputs=[x_column, y_column, group_column, size_column]
1484
+ ).then(
1485
+ lambda df, chart_type, x_col, y_col, group_col, size_col, color_scheme, patterns, title, width, height, show_grid, show_legend, agg_func, custom_colors:
1486
+ create_plot(
1487
+ df, chart_type, x_col, y_col,
1488
+ None if group_col == "" else group_col,
1489
+ None if size_col == "無" else size_col,
1490
+ color_scheme, patterns, title, width, height,
1491
+ show_grid, show_legend, agg_func, custom_colors
1492
+ ),
 
 
 
 
 
 
 
 
1493
  inputs=[
1494
+ data_state, chart_type, x_column, y_column,
1495
+ group_column, size_column,
1496
  color_scheme, patterns_state, chart_title, chart_width, chart_height,
1497
  show_grid, show_legend, agg_function, custom_colors_state
1498
  ],
1499
  outputs=[chart_output]
1500
  )
1501
 
1502
+ # 智能推薦功能
1503
+ recommend_button.click(
1504
+ recommend_chart_settings,
1505
+ inputs=[data_state],
1506
+ outputs=[recommendation_result]
1507
+ ).then(
1508
+ lambda rec: (
1509
+ rec.get("chart_type") if isinstance(rec, dict) and rec.get("chart_type") else None,
1510
+ rec.get("x_column") if isinstance(rec, dict) and rec.get("x_column") else None,
1511
+ rec.get("y_column") if isinstance(rec, dict) and rec.get("y_column") else None,
1512
+ rec.get("group_column") if isinstance(rec, dict) and rec.get("group_column") else "無",
1513
+ rec.get("agg_function") if isinstance(rec, dict) and rec.get("agg_function") else None
1514
+ ),
1515
+ inputs=[recommendation_result],
1516
+ outputs=[chart_type, x_column, y_column, group_column, agg_function]
1517
+ ).then(
1518
+ lambda df, chart_type, x_col, y_col, group_col, size_col, color_scheme, patterns, title, width, height, show_grid, show_legend, agg_func, custom_colors:
1519
+ create_plot(
1520
+ df, chart_type, x_col, y_col,
1521
+ None if group_col == "無" else group_col,
1522
+ None if size_col == "無" else size_col,
1523
+ color_scheme, patterns, title, width, height,
1524
+ show_grid, show_legend, agg_func, custom_colors
1525
+ ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1526
  inputs=[
1527
+ data_state, chart_type, x_column, y_column,
1528
+ group_column, size_column,
1529
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1530
  show_grid, show_legend, agg_function, custom_colors_state
1531
  ],
1532
  outputs=[chart_output]
1533
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1534
 
1535
+ # 其他輸入變化自動更新圖表
1536
+ for input_component in [x_column, y_column, group_column, size_column, agg_function, color_scheme]:
1537
+ input_component.change(
1538
+ lambda df, chart_type, x_col, y_col, group_col, size_col, color_scheme, patterns, title, width, height, show_grid, show_legend, agg_func, custom_colors:
1539
+ create_plot(
1540
+ df, chart_type, x_col, y_col,
1541
+ None if group_col == "無" else group_col,
1542
+ None if size_col == "無" else size_col,
1543
+ color_scheme, patterns, title, width, height,
1544
+ show_grid, show_legend, agg_func, custom_colors
1545
+ ),
1546
+ inputs=[
1547
+ data_state, chart_type, x_column, y_column,
1548
+ group_column, size_column,
1549
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1550
+ show_grid, show_legend, agg_function, custom_colors_state
1551
+ ],
1552
+ outputs=[chart_output]
1553
+ )
1554
 
1555
  # 啟動應用
1556
  demo.launch()