s880453 commited on
Commit
d823ce6
·
verified ·
1 Parent(s): fa51f3d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +866 -79
app.py CHANGED
@@ -6,67 +6,611 @@ import plotly.graph_objects as go
6
  import io
7
  import base64
8
  from PIL import Image
 
 
 
9
 
10
- # 初始化默認數據
11
- default_data = pd.DataFrame({
12
- "類別": ["A", "B", "C", "D", "E"],
13
- "數值": [10, 20, 15, 25, 30]
14
- })
 
 
 
 
15
 
16
- # 可用的顏色方案
17
  COLOR_SCHEMES = {
18
  "默認": px.colors.qualitative.Plotly,
 
 
 
 
 
 
 
 
 
 
 
19
  "藍綠色系": px.colors.sequential.Blues,
20
  "紅色系": px.colors.sequential.Reds,
21
  "綠色系": px.colors.sequential.Greens,
22
- "彩虹色": px.colors.sequential.Turbo
 
23
  }
24
 
25
- def create_plot(df, chart_type, x_column, y_column, color_scheme, title, width, height, show_grid, show_legend):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  """創建圖表函數"""
27
 
28
  # 數據預處理
29
  if df is None or df.empty:
30
- df = default_data
31
 
32
  # 確保列存在
33
- if x_column not in df.columns:
34
- x_column = df.columns[0] if len(df.columns) > 0 else "類別"
35
-
36
- if y_column not in df.columns:
37
- y_column = df.columns[1] if len(df.columns) > 1 else "數值"
38
 
39
  # 獲取選擇的顏色方案
40
  colors = COLOR_SCHEMES[color_scheme]
41
 
 
 
 
 
 
 
 
 
42
  # 設置圖表參數
43
  fig_params = {
44
- "width": width,
45
- "height": height,
46
  "title": title
47
  }
48
 
49
- # 基於選擇的圖表類型創建圖表
50
- if chart_type == "長條圖":
51
- fig = px.bar(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
52
-
53
- elif chart_type == "折線圖":
54
- fig = px.line(df, x=x_column, y=y_column, markers=True, color_discrete_sequence=colors, **fig_params)
55
-
56
- elif chart_type == "圓餅圖":
57
- fig = px.pie(df, names=x_column, values=y_column, color_discrete_sequence=colors, **fig_params)
58
 
59
- elif chart_type == "散點圖":
60
- fig = px.scatter(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
61
-
62
- elif chart_type == "區域圖":
63
- fig = px.area(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- else: # 默認使用長條圖
66
- fig = px.bar(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
 
 
 
 
 
 
67
 
68
- # 設置網格
69
  fig.update_layout(
 
 
70
  showlegend=show_legend,
71
  xaxis=dict(showgrid=show_grid),
72
  yaxis=dict(showgrid=show_grid)
@@ -106,8 +650,8 @@ def parse_data(csv_data):
106
  if ',' in first_line:
107
  # 優先使用逗號作為分隔符
108
  df = pd.read_csv(io.StringIO(csv_data), sep=',')
109
- elif ' ' in first_line:
110
- # 如果沒有逗號但有空格,使用空格作為分隔符
111
  df = pd.read_csv(io.StringIO(csv_data), sep='\\s+')
112
  else:
113
  # 默認使用逗號
@@ -156,38 +700,61 @@ def update_columns(df):
156
  """更新列選擇下拉菜單"""
157
  if df is None or df.empty:
158
  # 默認列
159
- return gr.Dropdown(choices=["類別", "數值"], value="類別"), gr.Dropdown(choices=["類別", "數值"], value="數值")
160
 
161
  columns = df.columns.tolist()
162
  x_dropdown = gr.Dropdown(choices=columns, value=columns[0] if columns else None)
163
  y_dropdown = gr.Dropdown(choices=columns, value=columns[1] if len(columns) > 1 else columns[0])
 
 
164
 
165
- return x_dropdown, y_dropdown
166
 
167
- def download_figure(fig):
168
  """導出圖表為圖像"""
169
  if fig is None:
170
  return None, "沒有圖表可以導出"
171
 
172
  try:
173
- # 將Plotly圖表轉換為PNG圖像
174
- img_bytes = fig.to_image(format="png")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- # 創建PIL圖像對象
177
- img = Image.open(io.BytesIO(img_bytes))
178
 
179
- return img, "圖表已成功導出為PNG圖像"
180
 
181
  except Exception as e:
182
  return None, f"導出圖表時出錯: {str(e)}"
183
 
184
  # 建立Gradio界面
185
- with gr.Blocks(title="數據可視化工具") as demo:
186
- gr.Markdown("# 數據可視化工具")
187
- gr.Markdown("上傳CSV或Excel文件,或直接在下方輸入數據來創建各種圖表")
188
 
189
  # 狀態變量
190
  data_state = gr.State(None)
 
 
191
 
192
  with gr.Tabs():
193
  # 數據輸入頁籤
@@ -199,7 +766,9 @@ with gr.Blocks(title="數據可視化工具") as demo:
199
  upload_status = gr.Textbox(label="上傳狀態")
200
 
201
  with gr.Column():
202
- csv_input = gr.Textbox(label="或直接輸入數據(逗號或空格分隔)", 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", lines=10)
 
 
203
  parse_button = gr.Button("解析數據")
204
  parse_status = gr.Textbox(label="解析狀態")
205
 
@@ -215,15 +784,19 @@ with gr.Blocks(title="數據可視化工具") as demo:
215
  # 圖表創建頁籤
216
  with gr.TabItem("圖表創建"):
217
  with gr.Row():
218
- with gr.Column():
219
  chart_type = gr.Dropdown(
220
- ["長條圖", "折線圖", "圓餅圖", "散點圖", "區域圖"],
221
  label="圖表類型",
222
  value="長條圖"
223
  )
224
 
225
- x_column = gr.Dropdown(["類別"], label="X軸(或類別)")
226
- y_column = gr.Dropdown(["數值"], label="Y軸(或數值)")
 
 
 
 
227
 
228
  chart_title = gr.Textbox(label="圖表標題", placeholder="我的數據圖表")
229
 
@@ -233,19 +806,92 @@ with gr.Blocks(title="數據可視化工具") as demo:
233
  value="默認"
234
  )
235
 
236
- with gr.Column():
 
 
 
 
 
 
 
237
  chart_width = gr.Slider(300, 1200, 700, label="圖表寬度")
238
  chart_height = gr.Slider(300, 800, 500, label="圖表高度")
239
 
240
- show_grid = gr.Checkbox(label="顯示網格", value=True)
241
- show_legend = gr.Checkbox(label="顯示圖例", value=True)
242
-
243
- update_button = gr.Button("更新圖表")
244
- download_button = gr.Button("導出為PNG圖像")
245
 
 
246
  with gr.Row():
247
- chart_output = gr.Plot(label="圖表預覽")
248
- download_output = gr.Image(label="導出的圖表", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
  # 事件處理
251
  upload_button.click(
@@ -259,7 +905,7 @@ with gr.Blocks(title="數據可視化工具") as demo:
259
  ).then(
260
  update_columns,
261
  inputs=[data_state],
262
- outputs=[x_column, y_column]
263
  )
264
 
265
  parse_button.click(
@@ -273,7 +919,7 @@ with gr.Blocks(title="數據可視化工具") as demo:
273
  ).then(
274
  update_columns,
275
  inputs=[data_state],
276
- outputs=[x_column, y_column]
277
  )
278
 
279
  export_button.click(
@@ -282,43 +928,184 @@ with gr.Blocks(title="數據可視化工具") as demo:
282
  outputs=[export_result, export_status]
283
  )
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  update_button.click(
286
- create_plot,
287
- inputs=[data_state, chart_type, x_column, y_column, color_scheme,
288
- chart_title, chart_width, chart_height, show_grid, show_legend],
 
 
 
 
 
 
 
 
 
 
 
289
  outputs=[chart_output]
290
  )
291
 
 
292
  download_button.click(
293
  download_figure,
294
- inputs=[chart_output],
295
- outputs=[download_output, gr.Textbox(label="下載狀態")]
296
- ).then(
297
- lambda: gr.update(visible=True),
298
- outputs=[download_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  )
300
 
301
- # 自動顯示圖表預覽
 
 
 
 
 
 
 
 
 
 
 
 
302
  chart_type.change(
303
- create_plot,
304
- inputs=[data_state, chart_type, x_column, y_column, color_scheme,
305
- chart_title, chart_width, chart_height, show_grid, show_legend],
 
 
 
306
  outputs=[chart_output]
307
  )
308
 
309
  x_column.change(
310
- create_plot,
311
- inputs=[data_state, chart_type, x_column, y_column, color_scheme,
312
- chart_title, chart_width, chart_height, show_grid, show_legend],
 
 
 
313
  outputs=[chart_output]
314
  )
315
 
316
  y_column.change(
317
- create_plot,
318
- inputs=[data_state, chart_type, x_column, y_column, color_scheme,
319
- chart_title, chart_width, chart_height, show_grid, show_legend],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  outputs=[chart_output]
321
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
  # 啟動應用
324
  demo.launch()
 
6
  import io
7
  import base64
8
  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 = [
15
+ "長條圖", "堆疊長條圖", "群組長條圖", "水平長條圖",
16
+ "折線圖", "多重折線圖", "階梯折線圖",
17
+ "圓餅圖", "環形圖", "散點圖", "氣泡圖",
18
+ "區域圖", "堆疊區域圖", "雷達圖", "熱力圖",
19
+ "箱型圖", "小提琴圖", "漏斗圖", "樹狀圖",
20
+ "直方圖", "極座標圖", "甘特圖"
21
+ ]
22
 
23
+ # 可用的顏色方案 (美觀且有實用價值的顏色選擇)
24
  COLOR_SCHEMES = {
25
  "默認": px.colors.qualitative.Plotly,
26
+ "Pastel": px.colors.qualitative.Pastel,
27
+ "Safe": px.colors.qualitative.Safe,
28
+ "Vivid": px.colors.qualitative.Vivid,
29
+ "Prism": px.colors.qualitative.Prism,
30
+ "Antique": px.colors.qualitative.Antique,
31
+ "Bold": px.colors.qualitative.Bold,
32
+ "Pastel1": px.colors.qualitative.Pastel1,
33
+ "Pastel2": px.colors.qualitative.Pastel2,
34
+ "Set1": px.colors.qualitative.Set1,
35
+ "Set2": px.colors.qualitative.Set2,
36
+ "Set3": px.colors.qualitative.Set3,
37
  "藍綠色系": px.colors.sequential.Blues,
38
  "紅色系": px.colors.sequential.Reds,
39
  "綠色系": px.colors.sequential.Greens,
40
+ "紫色系": px.colors.sequential.Purples,
41
+ "灰度": px.colors.sequential.Greys,
42
  }
43
 
44
+ # 圖案填充類型 (黑白印刷用)
45
+ PATTERN_TYPES = [
46
+ "無", "/", "\\", "x", "-", "|", "+", ".", "*"
47
+ ]
48
+
49
+ # 統計函數選項
50
+ AGGREGATION_FUNCTIONS = [
51
+ "求和", "平均值", "最大值", "最小值", "計數", "中位數", "標準差", "變異數"
52
+ ]
53
+
54
+ def agg_function_map(func_name):
55
+ """映射中文統計函數名稱到Pandas函數"""
56
+ mapping = {
57
+ "求和": "sum",
58
+ "平均值": "mean",
59
+ "最大值": "max",
60
+ "最小值": "min",
61
+ "計數": "count",
62
+ "中位數": "median",
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
  # 數據預處理
74
  if df is None or df.empty:
75
+ return go.Figure()
76
 
77
  # 確保列存在
78
+ if x_column not in df.columns or y_column not in df.columns:
79
+ return go.Figure()
 
 
 
80
 
81
  # 獲取選擇的顏色方案
82
  colors = COLOR_SCHEMES[color_scheme]
83
 
84
+ # 將非數值列轉換為類別型
85
+ for col in df.columns:
86
+ if df[col].dtype == 'object' or df[col].dtype == 'string':
87
+ df[col] = df[col].astype('category')
88
+
89
+ # 使用pandas的groupby進行數據聚合
90
+ agg_func = agg_function_map(agg_function)
91
+
92
  # 設置圖表參數
93
  fig_params = {
 
 
94
  "title": title
95
  }
96
 
97
+ # 創建基本圖形
98
+ fig = go.Figure()
 
 
 
 
 
 
 
99
 
100
+ try:
101
+ # 基於選擇的圖表類型創建圖表
102
+ if chart_type == "長條圖":
103
+ # 進行數據分組和聚合
104
+ if group_column and group_column in df.columns and group_column != x_column:
105
+ grouped_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
106
+ fig = px.bar(grouped_df, x=x_column, y=y_column, color=group_column,
107
+ color_discrete_sequence=colors, **fig_params)
108
+ else:
109
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
110
+ fig = px.bar(grouped_df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
111
+
112
+ # 應用自定義顏色和圖案
113
+ if patterns and len(patterns) > 0:
114
+ for i, bar in enumerate(fig.data):
115
+ pattern_index = i % len(patterns)
116
+ if patterns[pattern_index] != "無":
117
+ bar.marker.pattern = {
118
+ 'shape': patterns[pattern_index],
119
+ 'solidity': 0.5
120
+ }
121
+
122
+ elif chart_type == "堆疊長條圖":
123
+ if group_column and group_column in df.columns:
124
+ grouped_df = df.pivot_table(index=x_column, columns=group_column,
125
+ values=y_column, aggfunc=agg_func).reset_index()
126
+ grouped_df = grouped_df.fillna(0)
127
+
128
+ # 取得所有類別
129
+ categories = grouped_df.columns.tolist()
130
+ categories.remove(x_column)
131
+
132
+ for i, category in enumerate(categories):
133
+ color = colors[i % len(colors)]
134
+ if category in custom_colors:
135
+ color = custom_colors[category]
136
+
137
+ pattern_shape = None
138
+ if patterns and i < len(patterns) and patterns[i] != "無":
139
+ pattern_shape = patterns[i]
140
+
141
+ fig.add_trace(go.Bar(
142
+ x=grouped_df[x_column],
143
+ y=grouped_df[category],
144
+ name=str(category),
145
+ marker_color=color,
146
+ marker_pattern_shape=pattern_shape
147
+ ))
148
+
149
+ fig.update_layout(barmode='stack')
150
+ else:
151
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
152
+ fig = px.bar(grouped_df, x=x_column, y=y_column, **fig_params)
153
+
154
+ elif chart_type == "群組長條圖":
155
+ if group_column and group_column in df.columns:
156
+ grouped_df = df.pivot_table(index=x_column, columns=group_column,
157
+ values=y_column, aggfunc=agg_func).reset_index()
158
+ grouped_df = grouped_df.fillna(0)
159
+
160
+ # 取得所有類別
161
+ categories = grouped_df.columns.tolist()
162
+ categories.remove(x_column)
163
+
164
+ for i, category in enumerate(categories):
165
+ color = colors[i % len(colors)]
166
+ if category in custom_colors:
167
+ color = custom_colors[category]
168
+
169
+ pattern_shape = None
170
+ if patterns and i < len(patterns) and patterns[i] != "無":
171
+ pattern_shape = patterns[i]
172
+
173
+ fig.add_trace(go.Bar(
174
+ x=grouped_df[x_column],
175
+ y=grouped_df[category],
176
+ name=str(category),
177
+ marker_color=color,
178
+ marker_pattern_shape=pattern_shape
179
+ ))
180
+
181
+ fig.update_layout(barmode='group')
182
+ else:
183
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
184
+ fig = px.bar(grouped_df, x=x_column, y=y_column, **fig_params)
185
+
186
+ elif chart_type == "水平長條圖":
187
+ if group_column and group_column in df.columns:
188
+ grouped_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
189
+ fig = px.bar(grouped_df, y=x_column, x=y_column, color=group_column,
190
+ color_discrete_sequence=colors, orientation='h', **fig_params)
191
+ else:
192
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
193
+ fig = px.bar(grouped_df, y=x_column, x=y_column, orientation='h',
194
+ color_discrete_sequence=colors, **fig_params)
195
+
196
+ elif chart_type == "折線圖":
197
+ if group_column and group_column in df.columns and group_column != x_column:
198
+ grouped_df = df.groupby([x_column, group_column])[y_column].agg(agg_func).reset_index()
199
+ fig = px.line(grouped_df, x=x_column, y=y_column, color=group_column,
200
+ color_discrete_sequence=colors, markers=True, **fig_params)
201
+
202
+ # 根據自定義顏色和線型
203
+ for i, trace in enumerate(fig.data):
204
+ if i < len(patterns) and patterns[i] != "無":
205
+ if patterns[i] == '/':
206
+ trace.line.dash = 'dash'
207
+ elif patterns[i] == '\\':
208
+ trace.line.dash = 'dot'
209
+ elif patterns[i] == 'x':
210
+ trace.line.dash = 'dashdot'
211
+ elif patterns[i] == '-':
212
+ trace.line.dash = 'longdash'
213
+ else:
214
+ trace.line.dash = 'solid'
215
+ else:
216
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
217
+ fig = px.line(grouped_df, x=x_column, y=y_column, markers=True,
218
+ color_discrete_sequence=colors, **fig_params)
219
+
220
+ elif chart_type == "多重折線圖":
221
+ if group_column and group_column in df.columns:
222
+ grouped_df = df.pivot_table(index=x_column, columns=group_column,
223
+ values=y_column, aggfunc=agg_func).reset_index()
224
+ grouped_df = grouped_df.fillna(0)
225
+
226
+ # 取得所有類別
227
+ categories = grouped_df.columns.tolist()
228
+ categories.remove(x_column)
229
+
230
+ for i, category in enumerate(categories):
231
+ color = colors[i % len(colors)]
232
+ if category in custom_colors:
233
+ color = custom_colors[category]
234
+
235
+ line_dash = 'solid'
236
+ if patterns and i < len(patterns) and patterns[i] != "無":
237
+ if patterns[i] == '/':
238
+ line_dash = 'dash'
239
+ elif patterns[i] == '\\':
240
+ line_dash = 'dot'
241
+ elif patterns[i] == 'x':
242
+ line_dash = 'dashdot'
243
+ elif patterns[i] == '-':
244
+ line_dash = 'longdash'
245
+
246
+ fig.add_trace(go.Scatter(
247
+ x=grouped_df[x_column],
248
+ y=grouped_df[category],
249
+ mode='lines+markers',
250
+ name=str(category),
251
+ line=dict(color=color, dash=line_dash),
252
+ marker=dict(color=color)
253
+ ))
254
+ else:
255
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
256
+ fig = px.line(grouped_df, x=x_column, y=y_column, markers=True, **fig_params)
257
+
258
+ elif chart_type == "階梯折線圖":
259
+ if group_column and group_column in df.columns:
260
+ grouped_df = df.pivot_table(index=x_column, columns=group_column,
261
+ values=y_column, aggfunc=agg_func).reset_index()
262
+ grouped_df = grouped_df.fillna(0)
263
+
264
+ # 取得所有類別
265
+ categories = grouped_df.columns.tolist()
266
+ categories.remove(x_column)
267
+
268
+ for i, category in enumerate(categories):
269
+ color = colors[i % len(colors)]
270
+ if category in custom_colors:
271
+ color = custom_colors[category]
272
+
273
+ line_dash = 'solid'
274
+ if patterns and i < len(patterns) and patterns[i] != "無":
275
+ if patterns[i] == '/':
276
+ line_dash = 'dash'
277
+ elif patterns[i] == '\\':
278
+ line_dash = 'dot'
279
+ elif patterns[i] == 'x':
280
+ line_dash = 'dashdot'
281
+ elif patterns[i] == '-':
282
+ line_dash = 'longdash'
283
+
284
+ fig.add_trace(go.Scatter(
285
+ x=grouped_df[x_column],
286
+ y=grouped_df[category],
287
+ mode='lines',
288
+ name=str(category),
289
+ line=dict(shape='hv', color=color, dash=line_dash)
290
+ ))
291
+ else:
292
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
293
+ fig.add_trace(go.Scatter(
294
+ x=grouped_df[x_column],
295
+ y=grouped_df[y_column],
296
+ mode='lines',
297
+ line=dict(shape='hv', color=colors[0])
298
+ ))
299
+
300
+ elif chart_type == "圓餅圖":
301
+ # 圓餅圖只需要分類和對應的數值
302
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
303
+
304
+ # 設置自定義顏色
305
+ pie_colors = colors
306
+ if custom_colors and len(custom_colors) > 0:
307
+ pie_colors = [custom_colors.get(cat, colors[i % len(colors)])
308
+ for i, cat in enumerate(grouped_df[x_column])]
309
+
310
+ # 設置自定義圖案
311
+ pattern_shapes = None
312
+ if patterns and len(patterns) > 0:
313
+ pattern_shapes = [p if p != "無" else None for p in patterns]
314
+ if len(pattern_shapes) < len(grouped_df):
315
+ # 重複圖案以匹配數據長度
316
+ pattern_shapes = pattern_shapes * (len(grouped_df) // len(pattern_shapes) + 1)
317
+ pattern_shapes = pattern_shapes[:len(grouped_df)]
318
+
319
+ fig = px.pie(grouped_df, names=x_column, values=y_column,
320
+ color_discrete_sequence=pie_colors, **fig_params)
321
+
322
+ # 應用圖案填充
323
+ if pattern_shapes:
324
+ for i, trace in enumerate(fig.data):
325
+ for j, path in enumerate(trace.marker.pattern):
326
+ if j < len(pattern_shapes) and pattern_shapes[j]:
327
+ path.shape = pattern_shapes[j]
328
+ path.solidity = 0.5
329
+
330
+ elif chart_type == "環形圖":
331
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
332
+
333
+ # 設置自定義顏色
334
+ pie_colors = colors
335
+ if custom_colors and len(custom_colors) > 0:
336
+ pie_colors = [custom_colors.get(cat, colors[i % len(colors)])
337
+ for i, cat in enumerate(grouped_df[x_column])]
338
+
339
+ fig = px.pie(grouped_df, names=x_column, values=y_column, hole=0.4,
340
+ color_discrete_sequence=pie_colors, **fig_params)
341
+
342
+ # 應用圖案填充
343
+ if patterns and len(patterns) > 0:
344
+ for i, trace in enumerate(fig.data):
345
+ trace.marker.pattern = {
346
+ 'shape': patterns[i % len(patterns)] if patterns[i % len(patterns)] != "無" else None,
347
+ 'solidity': 0.5
348
+ }
349
+
350
+ elif chart_type == "散點圖":
351
+ if group_column and group_column in df.columns:
352
+ fig = px.scatter(df, x=x_column, y=y_column, color=group_column,
353
+ color_discrete_sequence=colors, **fig_params)
354
+
355
+ if size_column and size_column in df.columns:
356
+ fig = px.scatter(df, x=x_column, y=y_column, color=group_column,
357
+ size=size_column, color_discrete_sequence=colors, **fig_params)
358
+ else:
359
+ fig = px.scatter(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
360
+
361
+ # 應用散點圖符號
362
+ if patterns and len(patterns) > 0:
363
+ for i, trace in enumerate(fig.data):
364
+ pattern_idx = i % len(patterns)
365
+ if patterns[pattern_idx] != "無":
366
+ if patterns[pattern_idx] == '/':
367
+ trace.marker.symbol = 'diamond'
368
+ elif patterns[pattern_idx] == '\\':
369
+ trace.marker.symbol = 'square'
370
+ elif patterns[pattern_idx] == 'x':
371
+ trace.marker.symbol = 'x'
372
+ elif patterns[pattern_idx] == '-':
373
+ trace.marker.symbol = 'line-ew'
374
+ elif patterns[pattern_idx] == '|':
375
+ trace.marker.symbol = 'line-ns'
376
+ elif patterns[pattern_idx] == '+':
377
+ trace.marker.symbol = 'cross'
378
+ elif patterns[pattern_idx] == '.':
379
+ trace.marker.symbol = 'circle'
380
+ elif patterns[pattern_idx] == '*':
381
+ trace.marker.symbol = 'star'
382
+ else:
383
+ trace.marker.symbol = 'circle'
384
+
385
+ elif chart_type == "氣泡圖":
386
+ if size_column and size_column in df.columns:
387
+ if group_column and group_column in df.columns:
388
+ fig = px.scatter(df, x=x_column, y=y_column, color=group_column,
389
+ size=size_column, size_max=30,
390
+ color_discrete_sequence=colors, **fig_params)
391
+ else:
392
+ fig = px.scatter(df, x=x_column, y=y_column, size=size_column,
393
+ size_max=30, color_discrete_sequence=colors, **fig_params)
394
+ else:
395
+ # 如果沒有指定大小列,則退回到一般散點圖
396
+ fig = px.scatter(df, x=x_column, y=y_column, color_discrete_sequence=colors, **fig_params)
397
+
398
+ elif chart_type == "區域圖":
399
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
400
+ fig = px.area(grouped_df, x=x_column, y=y_column,
401
+ color_discrete_sequence=colors, **fig_params)
402
+
403
+ # 應用填充圖案
404
+ if patterns and len(patterns) > 0 and patterns[0] != "無":
405
+ for trace in fig.data:
406
+ trace.fill = 'tozeroy'
407
+ # Plotly的區域圖不直接支持填充圖案,但可以使用線條樣式來模擬
408
+ if patterns[0] == '/':
409
+ trace.line.dash = 'dash'
410
+ elif patterns[0] == '\\':
411
+ trace.line.dash = 'dot'
412
+ elif patterns[0] == 'x':
413
+ trace.line.dash = 'dashdot'
414
+
415
+ elif chart_type == "堆疊區域圖":
416
+ if group_column and group_column in df.columns:
417
+ # 創建樞紐表以便於繪製堆疊面積圖
418
+ pivot_df = df.pivot_table(index=x_column, columns=group_column,
419
+ values=y_column, aggfunc=agg_func).reset_index()
420
+ pivot_df = pivot_df.fillna(0)
421
+
422
+ # 獲取類別列
423
+ categories = pivot_df.columns.tolist()
424
+ categories.remove(x_column)
425
+
426
+ # 建立堆疊區域圖
427
+ for i, category in enumerate(categories):
428
+ color = colors[i % len(colors)]
429
+ if category in custom_colors:
430
+ color = custom_colors[category]
431
+
432
+ # 添加區域軌跡
433
+ fig.add_trace(go.Scatter(
434
+ x=pivot_df[x_column],
435
+ y=pivot_df[category],
436
+ mode='lines',
437
+ line=dict(width=0.5, color=color),
438
+ stackgroup='one',
439
+ name=str(category)
440
+ ))
441
+ else:
442
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
443
+ fig = px.area(grouped_df, x=x_column, y=y_column, **fig_params)
444
+
445
+ elif chart_type == "雷達圖":
446
+ if group_column and group_column in df.columns:
447
+ # 對於每個組創建一個雷達圖的軌跡
448
+ groups = df[group_column].unique()
449
+
450
+ for i, group in enumerate(groups):
451
+ group_data = df[df[group_column] == group]
452
+ grouped_df = group_data.groupby(x_column)[y_column].agg(agg_func).reset_index()
453
+
454
+ theta = grouped_df[x_column].tolist()
455
+ r = grouped_df[y_column].tolist()
456
+
457
+ # 封閉雷達圖
458
+ theta.append(theta[0])
459
+ r.append(r[0])
460
+
461
+ fig.add_trace(go.Scatterpolar(
462
+ r=r,
463
+ theta=theta,
464
+ fill='toself',
465
+ name=str(group),
466
+ line_color=colors[i % len(colors)]
467
+ ))
468
+ else:
469
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
470
+
471
+ theta = grouped_df[x_column].tolist()
472
+ r = grouped_df[y_column].tolist()
473
+
474
+ # 封閉雷達圖
475
+ theta.append(theta[0])
476
+ r.append(r[0])
477
+
478
+ fig.add_trace(go.Scatterpolar(
479
+ r=r,
480
+ theta=theta,
481
+ fill='toself',
482
+ line_color=colors[0]
483
+ ))
484
+
485
+ fig.update_layout(
486
+ polar=dict(
487
+ radialaxis=dict(
488
+ visible=True
489
+ )
490
+ )
491
+ )
492
+
493
+ elif chart_type == "熱力圖":
494
+ # 熱力圖需要兩個分類變量和一個連續變量
495
+ if group_column and group_column in df.columns:
496
+ # 創建樞紐表
497
+ pivot_df = df.pivot_table(index=x_column, columns=group_column,
498
+ values=y_column, aggfunc=agg_func)
499
+
500
+ # 創建熱力圖
501
+ fig = px.imshow(pivot_df, color_continuous_scale=px.colors.sequential.Viridis, **fig_params)
502
+ fig.update_layout(coloraxis_showscale=True)
503
+ else:
504
+ # 如果沒有組列,則沒有足夠的維度來創建熱力圖
505
+ fig = go.Figure()
506
+ fig.add_annotation(
507
+ text="熱力圖需要選擇一個分組列",
508
+ showarrow=False,
509
+ font=dict(size=16)
510
+ )
511
+
512
+ elif chart_type == "箱型圖":
513
+ if group_column and group_column in df.columns:
514
+ fig = px.box(df, x=group_column, y=y_column, color=group_column,
515
+ color_discrete_sequence=colors, **fig_params)
516
+ else:
517
+ fig = px.box(df, y=y_column, color_discrete_sequence=colors, **fig_params)
518
+
519
+ elif chart_type == "小提琴圖":
520
+ if group_column and group_column in df.columns:
521
+ fig = px.violin(df, x=group_column, y=y_column, color=group_column,
522
+ box=True, points="all", color_discrete_sequence=colors, **fig_params)
523
+ else:
524
+ fig = px.violin(df, y=y_column, box=True, points="all",
525
+ color_discrete_sequence=colors, **fig_params)
526
+
527
+ elif chart_type == "漏斗圖":
528
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
529
+
530
+ # 按值排序
531
+ grouped_df = grouped_df.sort_values(by=y_column, ascending=False)
532
+
533
+ # 創建漏斗圖
534
+ fig = go.Figure(go.Funnel(
535
+ y=grouped_df[x_column],
536
+ x=grouped_df[y_column],
537
+ textposition="inside",
538
+ textinfo="value+percent initial",
539
+ marker={"color": colors[:len(grouped_df)]}
540
+ ))
541
+
542
+ fig.update_layout(title=title)
543
+
544
+ elif chart_type == "樹狀圖":
545
+ if group_column and group_column in df.columns:
546
+ # 創建層次結構
547
+ fig = px.treemap(df, path=[group_column, x_column], values=y_column,
548
+ color_discrete_sequence=colors, **fig_params)
549
+ else:
550
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
551
+ fig = px.treemap(grouped_df, path=[x_column], values=y_column,
552
+ color_discrete_sequence=colors, **fig_params)
553
+
554
+ elif chart_type == "直方圖":
555
+ # 直方圖顯示單一變量的分佈
556
+ fig = px.histogram(df, x=x_column, color=group_column if group_column else None,
557
+ color_discrete_sequence=colors, **fig_params)
558
+
559
+ elif chart_type == "極座標圖":
560
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
561
+
562
+ # 創建極座標條形圖
563
+ fig = px.bar_polar(grouped_df, r=y_column, theta=x_column,
564
+ color=x_column, color_discrete_sequence=colors, **fig_params)
565
+
566
+ elif chart_type == "甘特圖":
567
+ # 甘特圖需要開始和結束時間
568
+ # 假設x_column是任務名稱,y_column是開始時間,group_column是結束時間
569
+ if group_column and group_column in df.columns:
570
+ # 確保日期時間格式
571
+ try:
572
+ df[y_column] = pd.to_datetime(df[y_column])
573
+ df[group_column] = pd.to_datetime(df[group_column])
574
+
575
+ # 創建甘特圖
576
+ fig = px.timeline(df, x_start=y_column, x_end=group_column, y=x_column,
577
+ color=size_column if size_column else None,
578
+ color_discrete_sequence=colors, **fig_params)
579
+
580
+ fig.update_layout(xaxis_type="date")
581
+ except:
582
+ fig = go.Figure()
583
+ fig.add_annotation(
584
+ text="無法將列轉換為日期格式,甘特圖需要日期時間格式的開始和結束列",
585
+ showarrow=False,
586
+ font=dict(size=14)
587
+ )
588
+ else:
589
+ fig = go.Figure()
590
+ fig.add_annotation(
591
+ text="甘特圖需要開始日期和結束日期列",
592
+ showarrow=False,
593
+ font=dict(size=16)
594
+ )
595
+
596
+ else:
597
+ # 默認圖表類型
598
+ grouped_df = df.groupby(x_column)[y_column].agg(agg_func).reset_index()
599
+ fig = px.bar(grouped_df, x=x_column, y=y_column, **fig_params)
600
 
601
+ except Exception as e:
602
+ # 處理創建圖表時的錯誤
603
+ fig = go.Figure()
604
+ fig.add_annotation(
605
+ text=f"創建圖表時出錯: {str(e)}",
606
+ showarrow=False,
607
+ font=dict(size=14)
608
+ )
609
 
610
+ # 設置網格和圖例
611
  fig.update_layout(
612
+ width=width,
613
+ height=height,
614
  showlegend=show_legend,
615
  xaxis=dict(showgrid=show_grid),
616
  yaxis=dict(showgrid=show_grid)
 
650
  if ',' in first_line:
651
  # 優先使用逗號作為分隔符
652
  df = pd.read_csv(io.StringIO(csv_data), sep=',')
653
+ elif ' ' in first_line or '\t' in first_line:
654
+ # 如果沒有逗號但有空格或制表符,使用空格作為分隔符
655
  df = pd.read_csv(io.StringIO(csv_data), sep='\\s+')
656
  else:
657
  # 默認使用逗號
 
700
  """更新列選擇下拉菜單"""
701
  if df is None or df.empty:
702
  # 默認列
703
+ return gr.Dropdown(choices=["類別", "數值"], value="類別"), gr.Dropdown(choices=["類別", "數值"], value="數值"), gr.Dropdown(choices=["無", "類別", "數值"], value="無"), gr.Dropdown(choices=["無", "類別", "數值"], value="無")
704
 
705
  columns = df.columns.tolist()
706
  x_dropdown = gr.Dropdown(choices=columns, value=columns[0] if columns else None)
707
  y_dropdown = gr.Dropdown(choices=columns, value=columns[1] if len(columns) > 1 else columns[0])
708
+ group_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
709
+ size_dropdown = gr.Dropdown(choices=["無"] + columns, value="無")
710
 
711
+ return x_dropdown, y_dropdown, group_dropdown, size_dropdown
712
 
713
+ def download_figure(fig, format_type="PNG"):
714
  """導出圖表為圖像"""
715
  if fig is None:
716
  return None, "沒有圖表可以導出"
717
 
718
  try:
719
+ # 選擇導出格式
720
+ if format_type == "PNG":
721
+ img_bytes = fig.to_image(format="png")
722
+ mime_type = "image/png"
723
+ ext = "png"
724
+ elif format_type == "SVG":
725
+ img_bytes = fig.to_image(format="svg")
726
+ mime_type = "image/svg+xml"
727
+ ext = "svg"
728
+ elif format_type == "PDF":
729
+ img_bytes = fig.to_image(format="pdf")
730
+ mime_type = "application/pdf"
731
+ ext = "pdf"
732
+ elif format_type == "JPEG":
733
+ img_bytes = fig.to_image(format="jpeg")
734
+ mime_type = "image/jpeg"
735
+ ext = "jpg"
736
+ else:
737
+ img_bytes = fig.to_image(format="png")
738
+ mime_type = "image/png"
739
+ ext = "png"
740
 
741
+ # 創建文件對象
742
+ filename = f"chart_export.{ext}"
743
 
744
+ return (img_bytes, filename, mime_type), f"圖表已成功導出為{format_type}格式"
745
 
746
  except Exception as e:
747
  return None, f"導出圖表時出錯: {str(e)}"
748
 
749
  # 建立Gradio界面
750
+ with gr.Blocks(title="進階數據可視化工具") as demo:
751
+ gr.Markdown("# 進階數據可視化工具")
752
+ gr.Markdown("上傳CSV或Excel文件,或直接在下方輸入數據來創建各種專業圖表")
753
 
754
  # 狀態變量
755
  data_state = gr.State(None)
756
+ custom_colors_state = gr.State({})
757
+ patterns_state = gr.State([])
758
 
759
  with gr.Tabs():
760
  # 數據輸入頁籤
 
766
  upload_status = gr.Textbox(label="上傳狀態")
767
 
768
  with gr.Column():
769
+ csv_input = gr.Textbox(label="或直接輸入數據(逗號或空格分隔)",
770
+ 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",
771
+ lines=10)
772
  parse_button = gr.Button("解析數據")
773
  parse_status = gr.Textbox(label="解析狀態")
774
 
 
784
  # 圖表創建頁籤
785
  with gr.TabItem("圖表創建"):
786
  with gr.Row():
787
+ with gr.Column(scale=1):
788
  chart_type = gr.Dropdown(
789
+ CHART_TYPES,
790
  label="圖表類型",
791
  value="長條圖"
792
  )
793
 
794
+ agg_function = gr.Dropdown(
795
+ AGGREGATION_FUNCTIONS,
796
+ label="聚合函數",
797
+ value="求和",
798
+ info="選擇如何彙總數據"
799
+ )
800
 
801
  chart_title = gr.Textbox(label="圖表標題", placeholder="我的數據圖表")
802
 
 
806
  value="默認"
807
  )
808
 
809
+ with gr.Column(scale=1):
810
+ # 軸和分組選擇
811
+ x_column = gr.Dropdown(["類別"], label="X軸(或類別)")
812
+ y_column = gr.Dropdown(["數值"], label="Y軸(或數值)")
813
+ group_column = gr.Dropdown(["無"], label="分組列(用於多系列圖表)")
814
+ size_column = gr.Dropdown(["無"], label="大小列(用於氣泡圖等)")
815
+
816
+ # 尺寸控制
817
  chart_width = gr.Slider(300, 1200, 700, label="圖表寬度")
818
  chart_height = gr.Slider(300, 800, 500, label="圖表高度")
819
 
820
+ # 顯示選項
821
+ with gr.Row():
822
+ show_grid = gr.Checkbox(label="顯示網格", value=True)
823
+ show_legend = gr.Checkbox(label="顯示圖例", value=True)
 
824
 
825
+ # 圖案和顏色自定義區
826
  with gr.Row():
827
+ with gr.Column():
828
+ gr.Markdown("### 圖案和顏色設置")
829
+ gr.Markdown("為圖表元素設置特定的填充圖案(適用於黑白印刷)和顏色")
830
+
831
+ # 動態添加圖案,先默認提供三個
832
+ with gr.Row():
833
+ pattern1 = gr.Dropdown(PATTERN_TYPES, label="圖案1", value="無")
834
+ pattern2 = gr.Dropdown(PATTERN_TYPES, label="圖案2", value="無")
835
+ pattern3 = gr.Dropdown(PATTERN_TYPES, label="圖案3", value="無")
836
+
837
+ # 自定義顏色區域
838
+ color_customization = gr.Textbox(
839
+ label="自定義顏色 (格式: 類別1:#FF0000,類別2:#00FF00)",
840
+ placeholder="A:#1f77b4,B:#ff7f0e,C:#2ca02c",
841
+ info="輸入類別名稱和十六進制顏色代碼,用逗號分隔多個項目"
842
+ )
843
+
844
+ with gr.Column():
845
+ # 按鈕區
846
+ update_button = gr.Button("更新圖表", variant="primary")
847
+
848
+ with gr.Row():
849
+ export_format = gr.Dropdown(
850
+ ["PNG", "SVG", "PDF", "JPEG"],
851
+ label="導出格式",
852
+ value="PNG"
853
+ )
854
+ download_button = gr.Button("導出圖表")
855
+
856
+ export_chart = gr.File(label="導出的圖表")
857
+ export_chart_status = gr.Textbox(label="導出狀態")
858
+
859
+ # 圖表預覽區
860
+ chart_output = gr.Plot(label="圖表預覽")
861
+
862
+ # 輔助函數
863
+ def parse_custom_colors(color_text):
864
+ """解析自定義顏色文本"""
865
+ custom_colors = {}
866
+ if color_text and color_text.strip():
867
+ try:
868
+ pairs = color_text.split(',')
869
+ for pair in pairs:
870
+ if ':' in pair:
871
+ key, value = pair.split(':', 1)
872
+ custom_colors[key.strip()] = value.strip()
873
+ except:
874
+ pass
875
+ return custom_colors
876
+
877
+ def update_patterns(p1, p2, p3):
878
+ """更新圖案列表"""
879
+ patterns = []
880
+ if p1:
881
+ patterns.append(p1)
882
+ if p2:
883
+ patterns.append(p2)
884
+ if p3:
885
+ patterns.append(p3)
886
+ return patterns
887
+
888
+ def process_group_column(group_col):
889
+ """處理分組列選擇"""
890
+ return None if group_col == "無" else group_col
891
+
892
+ def process_size_column(size_col):
893
+ """處理大小列選擇"""
894
+ return None if size_col == "無" else size_col
895
 
896
  # 事件處理
897
  upload_button.click(
 
905
  ).then(
906
  update_columns,
907
  inputs=[data_state],
908
+ outputs=[x_column, y_column, group_column, size_column]
909
  )
910
 
911
  parse_button.click(
 
919
  ).then(
920
  update_columns,
921
  inputs=[data_state],
922
+ outputs=[x_column, y_column, group_column, size_column]
923
  )
924
 
925
  export_button.click(
 
928
  outputs=[export_result, export_status]
929
  )
930
 
931
+ # 處理圖案和顏色設置
932
+ color_customization.change(
933
+ parse_custom_colors,
934
+ inputs=[color_customization],
935
+ outputs=[custom_colors_state]
936
+ )
937
+
938
+ pattern1.change(
939
+ update_patterns,
940
+ inputs=[pattern1, pattern2, pattern3],
941
+ outputs=[patterns_state]
942
+ )
943
+
944
+ pattern2.change(
945
+ update_patterns,
946
+ inputs=[pattern1, pattern2, pattern3],
947
+ outputs=[patterns_state]
948
+ )
949
+
950
+ pattern3.change(
951
+ update_patterns,
952
+ inputs=[pattern1, pattern2, pattern3],
953
+ outputs=[patterns_state]
954
+ )
955
+
956
+ # 更新圖表
957
  update_button.click(
958
+ 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:
959
+ create_plot(
960
+ df, chart_type, x_col, y_col,
961
+ None if group_col == "無" else group_col,
962
+ None if size_col == "無" else size_col,
963
+ color_scheme, patterns, title, width, height,
964
+ show_grid, show_legend, agg_func, custom_colors
965
+ ),
966
+ inputs=[
967
+ data_state, chart_type, x_column, y_column,
968
+ group_column, size_column,
969
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
970
+ show_grid, show_legend, agg_function, custom_colors_state
971
+ ],
972
  outputs=[chart_output]
973
  )
974
 
975
+ # 導出圖表
976
  download_button.click(
977
  download_figure,
978
+ inputs=[chart_output, export_format],
979
+ outputs=[export_chart, export_chart_status]
980
+ )
981
+
982
+ # 圖表類型改變時更新界面元素可見性
983
+ def update_element_visibility(chart_type):
984
+ """根據圖表類型更新UI元素的可見性"""
985
+ # 圓餅圖和環形圖不需要X軸,只需要類別和數值
986
+ is_pie_chart = chart_type in ["圓餅圖", "環形圖"]
987
+
988
+ # 氣泡圖需要額外的大小控制列
989
+ needs_size_column = chart_type in ["氣泡圖", "甘特圖", "樹狀圖"]
990
+
991
+ # 需要分組列的圖表類型
992
+ needs_group_column = chart_type in [
993
+ "群組長條圖", "堆疊長條圖", "多重折線圖", "堆疊區域圖",
994
+ "熱力圖", "雷達圖", "散點圖", "氣泡圖"
995
+ ]
996
+
997
+ return (
998
+ gr.update(visible=not is_pie_chart, label="類別列" if is_pie_chart else "X軸"),
999
+ gr.update(visible=True, label="數值列" if is_pie_chart else "Y軸"),
1000
+ gr.update(visible=needs_group_column, label="分組列"),
1001
+ gr.update(visible=needs_size_column, label="大小列")
1002
+ )
1003
+
1004
+ chart_type.change(
1005
+ update_element_visibility,
1006
+ inputs=[chart_type],
1007
+ outputs=[x_column, y_column, group_column, size_column]
1008
  )
1009
 
1010
+ # 圖表相關自動更新函數
1011
+ 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):
1012
+ """圖表自動更新函數,用於各種輸入改變時"""
1013
+ # 處理可能為"無"的選項
1014
+ group_column_value = None if group_col == "無" else group_col
1015
+ size_column_value = None if size_col == "無" else size_col
1016
+
1017
+ return create_plot(
1018
+ df, chart_type, x_col, y_col, group_column_value, size_column_value,
1019
+ color_scheme, patterns_list, title, width, height, show_grid, show_legend, agg_func, custom_colors
1020
+ )
1021
+
1022
+ # 添加自動更新事件
1023
  chart_type.change(
1024
+ auto_update_chart,
1025
+ inputs=[
1026
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1027
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1028
+ show_grid, show_legend, agg_function, custom_colors_state
1029
+ ],
1030
  outputs=[chart_output]
1031
  )
1032
 
1033
  x_column.change(
1034
+ auto_update_chart,
1035
+ inputs=[
1036
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1037
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1038
+ show_grid, show_legend, agg_function, custom_colors_state
1039
+ ],
1040
  outputs=[chart_output]
1041
  )
1042
 
1043
  y_column.change(
1044
+ auto_update_chart,
1045
+ inputs=[
1046
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1047
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1048
+ show_grid, show_legend, agg_function, custom_colors_state
1049
+ ],
1050
+ outputs=[chart_output]
1051
+ )
1052
+
1053
+ group_column.change(
1054
+ auto_update_chart,
1055
+ inputs=[
1056
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1057
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1058
+ show_grid, show_legend, agg_function, custom_colors_state
1059
+ ],
1060
+ outputs=[chart_output]
1061
+ )
1062
+
1063
+ agg_function.change(
1064
+ auto_update_chart,
1065
+ inputs=[
1066
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1067
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1068
+ show_grid, show_legend, agg_function, custom_colors_state
1069
+ ],
1070
  outputs=[chart_output]
1071
  )
1072
+
1073
+ color_scheme.change(
1074
+ auto_update_chart,
1075
+ inputs=[
1076
+ data_state, chart_type, x_column, y_column, group_column, size_column,
1077
+ color_scheme, patterns_state, chart_title, chart_width, chart_height,
1078
+ show_grid, show_legend, agg_function, custom_colors_state
1079
+ ],
1080
+ outputs=[chart_output]
1081
+ )
1082
+
1083
+ # 在啟動應用之前添加使用說明
1084
+ with demo:
1085
+ gr.Markdown("""
1086
+ ## 使用說明
1087
+
1088
+ ### 數據輸入
1089
+ - 上傳CSV或Excel文件,或在文本框中直接輸入數據
1090
+ - 第一行被視為欄位名稱(表頭),不會納入統計
1091
+ - 支持逗號分隔(CSV)或空格分隔的數據格式
1092
+
1093
+ ### 圖表創建
1094
+ - 選擇圖表類型:長條圖、折線圖、圓餅圖等多種專業圖表
1095
+ - 聚合函數:選擇如何彙總數據(求和、平均值、最大值等)
1096
+ - 分組列:用於創建多系列圖表,如按類別分組的長條圖
1097
+ - 大小列:用於氣泡圖等需要額外數值控制大小的圖表
1098
+
1099
+ ### 自定義選項
1100
+ - 圖案填充:為圖表元素設置填充圖案,特別適用於黑白印刷
1101
+ - 自定義顏色:為特定類別設置顏色,格式為"類別1:#FF0000,類別2:#00FF00"
1102
+ - 導出格式:支持PNG、SVG、PDF和JPEG格式導出
1103
+
1104
+ ### 注意事項
1105
+ - 不同圖表類型需要不同的數據組織形式
1106
+ - 圓餅圖和環形圖只需要類別和數值列
1107
+ - 複雜圖表(如熱力圖、雷達圖)需要適當的數據結構
1108
+ """)
1109
 
1110
  # 啟動應用
1111
  demo.launch()