s880453 commited on
Commit
c1d8626
·
verified ·
1 Parent(s): 19f7503

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -85
app.py CHANGED
@@ -1273,8 +1273,8 @@ def recommend_chart_settings(df):
1273
  """
1274
  Gradio 應用程式:進階數據可視化工具
1275
  作者:Gemini
1276
- 版本:5.0 (極簡化 - 單圖表 + Radio)
1277
- 描述:包含所有功能的完整程式碼,極度簡化 UI,使用 Radio 替代 Dropdown,移除自動更新。
1278
  """
1279
 
1280
  # =========================================
@@ -1423,7 +1423,7 @@ def parse_data(text_data):
1423
  except Exception as e: print(f"解析文本數據時發生未預期錯誤: {e}"); traceback.print_exc(); return None, f"❌ 解析數據時發生未預期錯誤: {e}"
1424
 
1425
  def update_columns_as_radio(df):
1426
- """修改:更新列選擇為 Radio 選項"""
1427
  no_data_choices = [NO_DATA_STR]
1428
  no_data_choices_with_none = [NONE_STR, NO_DATA_STR]
1429
 
@@ -1431,7 +1431,7 @@ def update_columns_as_radio(df):
1431
  # 返回空的 Radio 更新
1432
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1433
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1434
- return no_data_update, no_data_update, no_data_update_with_none, no_data_update_with_none
1435
 
1436
  try:
1437
  columns = df.columns.tolist()
@@ -1439,27 +1439,28 @@ def update_columns_as_radio(df):
1439
  if not valid_columns: # 如果沒有有效列
1440
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1441
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1442
- return no_data_update, no_data_update, no_data_update_with_none, no_data_update_with_none
1443
 
1444
  x_default = valid_columns[0]
1445
- # Y 軸預設選第二個(如果存在),否則選第一個
1446
- y_default = valid_columns[1] if len(valid_columns) > 1 else valid_columns[0]
1447
 
1448
- # 為 group size 添加 "無" 選項
 
1449
  group_choices = [NONE_STR] + valid_columns
1450
  size_choices = [NONE_STR] + valid_columns
1451
 
1452
  # 返回 Radio 更新對象
1453
- # 注意:如果列過多,Radio 會很長,但這是為了測試功能性
1454
  return (gr.Radio(choices=valid_columns, value=x_default, label="X軸 / 類別"),
1455
- gr.Radio(choices=valid_columns, value=y_default, label="Y軸 / 數值"),
1456
  gr.Radio(choices=group_choices, value=NONE_STR, label="分組列"),
1457
  gr.Radio(choices=size_choices, value=NONE_STR, label="大小列"))
1458
  except Exception as e:
1459
  print(f"更新列選項 (Radio) 時出錯: {e}")
1460
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1461
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1462
- return no_data_update, no_data_update, no_data_update_with_none, no_data_update_with_none
 
1463
 
1464
 
1465
  # =========================================
@@ -1475,10 +1476,9 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1475
  if isinstance(df, pd.DataFrame):
1476
  print(f" - df empty: {df.empty}", file=sys.stderr)
1477
  print(f" - df shape: {df.shape}", file=sys.stderr)
1478
- # print(f" - df head:\n{df.head()}", file=sys.stderr) # 打印頭部數據幫助調試
1479
  print(f" - chart_type: {chart_type}", file=sys.stderr)
1480
  print(f" - x_column: {x_column}", file=sys.stderr)
1481
- print(f" - y_column: {y_column}", file=sys.stderr)
1482
  print(f" - group_column: {group_column}", file=sys.stderr)
1483
  print(f" - size_column: {size_column}", file=sys.stderr)
1484
  print(f" - agg_func_name: {agg_func_name}", file=sys.stderr)
@@ -1492,22 +1492,26 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1492
  show_legend = True if show_legend_str == "是" else False
1493
 
1494
  # --- 1. 輸入驗證 (更嚴格) ---
1495
- if df is None or not isinstance(df, pd.DataFrame) or df.empty: # 嚴格檢查 df
1496
  raise ValueError("沒有有效的 DataFrame 數據可供繪圖。請先載入數據。")
1497
  if not chart_type: raise ValueError("請選擇圖表類型。")
1498
  if not agg_func_name: raise ValueError("請選擇聚合函數。")
1499
  if not x_column or x_column == NO_DATA_STR: raise ValueError("請選擇有效的 X 軸或類別列。")
1500
 
1501
- # 檢查列是否存在
1502
  if x_column not in df.columns: raise ValueError(f"X 軸列 '{x_column}' 不在數據中。可用列: {', '.join(df.columns)}")
1503
 
1504
- # 只有在非計數且非直方圖時才嚴格要求 Y
 
 
 
1505
  y_needed = agg_func_name != "計數" and chart_type not in ["直方圖"]
1506
  if y_needed:
1507
- if not y_column or y_column == NO_DATA_STR: raise ValueError("此圖表類型和聚合函數需要選擇有效的 Y 軸或數值列。")
1508
- if y_column not in df.columns: raise ValueError(f"Y 軸列 '{y_column}' 不在數據中。可用列: {', '.join(df.columns)}")
 
 
1509
  else:
1510
- y_column = None # 如果不需要 Y 軸,明確設為 None
1511
 
1512
  # 處理可選列
1513
  group_col = None if group_column == NONE_STR or not group_column else group_column
@@ -1520,44 +1524,44 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1520
  df_processed = df.copy()
1521
 
1522
  # --- 2. 數據類型轉換與準備 ---
1523
- # (與 V4 相同)
1524
  df_processed[x_column] = df_processed[x_column].astype(str)
1525
  if group_col: df_processed[group_col] = df_processed[group_col].astype(str)
1526
- if y_column:
1527
- try: df_processed[y_column] = pd.to_numeric(df_processed[y_column], errors='coerce')
1528
- except Exception as e: print(f"警告:轉換 Y 軸列 '{y_column}' 為數值時出錯: {e}")
1529
  if size_col:
1530
  try: df_processed[size_col] = pd.to_numeric(df_processed[size_col], errors='coerce')
1531
  except Exception as e: print(f"警告:轉換大小列 '{size_col}' 為數值時出錯: {e}")
1532
 
1533
  # --- 3. 數據聚合 (如果需要) ---
1534
- # (與 V4 相同)
1535
  needs_aggregation = chart_type not in ["散點圖", "氣泡圖", "直方圖", "箱型圖", "小提琴圖", "甘特圖"]
1536
  agg_df = None
1537
- y_col_agg = y_column
1538
  if needs_aggregation:
1539
  grouping_cols = [x_column] + ([group_col] if group_col else [])
1540
  invalid_grouping_cols = [col for col in grouping_cols if col not in df_processed.columns]
1541
  if invalid_grouping_cols: raise ValueError(f"以下分組/X軸列不在數據中: {', '.join(invalid_grouping_cols)}")
 
1542
  if agg_func_name == "計數":
1543
  agg_df = df_processed.groupby(grouping_cols, observed=False, dropna=False).size().reset_index(name='__count__')
1544
- y_col_agg = '__count__'
1545
  else:
1546
  agg_func_pd = agg_function_map(agg_func_name)
1547
- if not y_column: raise ValueError(f"聚合函數 '{agg_func_name}' 需要一個有效的 Y 軸數值列。")
1548
- if agg_func_pd not in ['first', 'last'] and not pd.api.types.is_numeric_dtype(df_processed[y_column]):
1549
- try: df_processed[y_column] = pd.to_numeric(df_processed[y_column], errors='raise')
1550
- except (ValueError, TypeError): raise ValueError(f"Y 軸列 '{y_column}' 必須是數值類型才能執行聚合 '{agg_func_name}'。")
1551
  try:
1552
- agg_df = df_processed.groupby(grouping_cols, observed=False, dropna=False)[y_column].agg(agg_func_pd).reset_index()
1553
- y_col_agg = y_column
1554
  except Exception as agg_e: raise ValueError(f"執行聚合 '{agg_func_name}' 時出錯: {agg_e}")
1555
  else:
1556
  agg_df = df_processed
1557
- y_col_agg = y_column
1558
 
1559
  if agg_df is None or agg_df.empty: raise ValueError("數據聚合後沒有產生有效結果。")
1560
  required_cols_for_plot = [x_column]
 
1561
  if y_col_agg: required_cols_for_plot.append(y_col_agg)
1562
  if group_col: required_cols_for_plot.append(group_col)
1563
  if size_col: required_cols_for_plot.append(size_col)
@@ -1570,89 +1574,98 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1570
  # --- 5. 創建圖表 (核心邏輯) ---
1571
  fig_params = {"data_frame": agg_df, "title": title, "color_discrete_sequence": colors, "width": width, "height": height}
1572
  if group_col and custom_colors_dict: fig_params["color_discrete_map"] = custom_colors_dict
 
 
1573
  effective_y = y_col_agg if y_needed or agg_func_name == "計數" else None
1574
 
1575
- # --- (繪圖邏輯 - 與 V4 相同,省略) ---
1576
- if chart_type == "長條圖":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1577
  if not effective_y: raise ValueError("長條圖需要 Y 軸數值或 '計數' 聚合。")
1578
- fig = px.bar(agg_df, x=x_column, y=effective_y, color=group_col, **fig_params)
1579
  elif chart_type == "堆疊長條圖":
1580
  if not effective_y: raise ValueError("堆疊長條圖需要 Y 軸數值或 '計數' 聚合。")
1581
- fig = px.bar(agg_df, x=x_column, y=effective_y, color=group_col, barmode='stack', **fig_params)
1582
  elif chart_type == "百分比堆疊長條圖":
1583
  if not effective_y: raise ValueError("百分比堆疊長條圖需要 Y 軸數值或 '計數' 聚合。")
1584
- fig = px.bar(agg_df, x=x_column, y=effective_y, color=group_col, barmode='relative', text_auto='.1%', **fig_params)
1585
  fig.update_layout(yaxis_title="百分比 (%)")
1586
  elif chart_type == "群組長條圖":
1587
  if not effective_y: raise ValueError("群組長條圖需要 Y 軸數值或 '計數' 聚合。")
1588
- fig = px.bar(agg_df, x=x_column, y=effective_y, color=group_col, barmode='group', **fig_params)
1589
  elif chart_type == "水平長條圖":
1590
  if not effective_y: raise ValueError("水平長條圖需要 Y 軸數值或 '計數' 聚合。")
1591
- fig = px.bar(agg_df, y=x_column, x=effective_y, color=group_col, orientation='h', **fig_params)
1592
  elif chart_type == "折線圖":
1593
  if not effective_y: raise ValueError("折線圖需要 Y 軸數值或 '計數' 聚合。")
1594
- fig = px.line(agg_df, x=x_column, y=effective_y, color=group_col, markers=True, **fig_params)
1595
  elif chart_type == "多重折線圖":
1596
  if not effective_y: raise ValueError("多重折線圖需要 Y 軸數值或 '計數' 聚合。")
1597
- fig = px.line(agg_df, x=x_column, y=effective_y, color=group_col, markers=True, **fig_params)
1598
  elif chart_type == "階梯折線圖":
1599
  if not effective_y: raise ValueError("階梯折線圖需要 Y 軸數值或 '計數' 聚合。")
1600
- fig = px.line(agg_df, x=x_column, y=effective_y, color=group_col, line_shape='hv', **fig_params)
1601
  elif chart_type == "區域圖":
1602
  if not effective_y: raise ValueError("區域圖需要 Y 軸數值或 '計數' 聚合。")
1603
- fig = px.area(agg_df, x=x_column, y=effective_y, color=group_col, **fig_params)
1604
  elif chart_type == "堆疊區域圖":
1605
  if not effective_y: raise ValueError("堆疊區域圖需要 Y 軸數值或 '計數' 聚合。")
1606
- fig = px.area(agg_df, x=x_column, y=effective_y, color=group_col, groupnorm=None, **fig_params)
1607
  elif chart_type == "百分比堆疊區域圖":
1608
  if not effective_y: raise ValueError("百分比堆疊區域圖需要 Y 軸數值或 '計數' 聚合。")
1609
- fig = px.area(agg_df, x=x_column, y=effective_y, color=group_col, groupnorm='percent', **fig_params)
1610
  fig.update_layout(yaxis_title="百分比 (%)")
1611
- elif chart_type == "圓餅圖":
1612
- if not effective_y: raise ValueError("圓餅圖需要 Y 軸數值或 '計數' 聚合。")
1613
- if group_col: print("警告:圓餅圖不支持分組列,已忽略。")
1614
- fig = px.pie(agg_df, names=x_column, values=effective_y, **fig_params)
1615
- if not group_col and custom_colors_dict: fig.update_traces(marker=dict(colors=[custom_colors_dict.get(str(cat), colors[i % len(colors)]) for i, cat in enumerate(agg_df[x_column])]))
1616
- elif chart_type == "環形圖":
1617
- if not effective_y: raise ValueError("環形圖需要 Y 軸數值或 '計數' 聚合。")
1618
- if group_col: print("警告:環形圖不支持分組列,已忽略。")
1619
- fig = px.pie(agg_df, names=x_column, values=effective_y, hole=0.4, **fig_params)
1620
- if not group_col and custom_colors_dict: fig.update_traces(marker=dict(colors=[custom_colors_dict.get(str(cat), colors[i % len(colors)]) for i, cat in enumerate(agg_df[x_column])]))
1621
  elif chart_type == "散點圖":
1622
- if not y_column: raise ValueError("散點圖需要選擇 Y 軸列。")
1623
- fig = px.scatter(agg_df, x=x_column, y=y_column, color=group_col, size=size_col, **fig_params)
1624
  elif chart_type == "氣泡圖":
1625
- if not y_column: raise ValueError("氣泡圖需要選擇 Y 軸列。")
1626
  if not size_col: raise ValueError("氣泡圖需要指定 '大小列'。")
1627
  if not pd.api.types.is_numeric_dtype(agg_df[size_col]): raise ValueError(f"大小列 '{size_col}' 必須是數值類型。")
1628
- fig = px.scatter(agg_df, x=x_column, y=y_column, color=group_col, size=size_col, size_max=60, **fig_params)
1629
  elif chart_type == "直方圖":
1630
  if not pd.api.types.is_numeric_dtype(agg_df[x_column]): raise ValueError(f"直方圖的 X 軸列 '{x_column}' 必須是數值類型。")
1631
- fig = px.histogram(agg_df, x=x_column, color=group_col, **fig_params); fig.update_layout(yaxis_title="計數")
1632
  elif chart_type == "箱型圖":
1633
- if not y_column: raise ValueError("箱型圖需要選擇 Y 軸列。")
1634
- if not pd.api.types.is_numeric_dtype(agg_df[y_column]): raise ValueError(f"箱型圖的 Y 軸列 '{y_column}' 必須是數值類型。")
1635
- fig = px.box(agg_df, x=group_col, y=y_column, color=group_col, **fig_params)
1636
- if not group_col: fig = px.box(agg_df, y=y_column, **fig_params)
1637
  elif chart_type == "小提琴圖":
1638
- if not y_column: raise ValueError("小提琴圖需要選擇 Y 軸列。")
1639
- if not pd.api.types.is_numeric_dtype(agg_df[y_column]): raise ValueError(f"小提琴圖的 Y 軸列 '{y_column}' 必須是數值類型。")
1640
- fig = px.violin(agg_df, x=group_col, y=y_column, color=group_col, box=True, points="all", **fig_params)
1641
- if not group_col: fig = px.violin(agg_df, y=y_column, box=True, points="all", **fig_params)
1642
- elif chart_type == "熱力圖":
1643
- if not effective_y: raise ValueError("熱力圖需要 Y 軸數值或 '計數' 聚合。")
1644
- if not group_col: raise ValueError("熱力圖需要 X 軸、Y 軸 和一個 分組列。")
1645
- try:
1646
- if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"熱力圖的值列 '{effective_y}' 必須是數值類型。")
1647
- pivot_df = pd.pivot_table(agg_df, values=effective_y, index=group_col, columns=x_column, aggfunc=agg_function_map(agg_func_name) if agg_func_name != "計數" else 'size')
1648
- fig = px.imshow(pivot_df, color_continuous_scale=px.colors.sequential.Viridis, aspect="auto", text_auto=True, **fig_params);
1649
- fig.update_layout(coloraxis_showscale=True)
1650
- except Exception as pivot_e: raise ValueError(f"創建熱力圖的數據透視表時出錯: {pivot_e}")
1651
  elif chart_type == "樹狀圖":
1652
  if not effective_y: raise ValueError("樹狀圖需要 Y 軸數值或 '計數' 聚合。")
1653
  path = [group_col, x_column] if group_col else [x_column]
1654
  if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"樹狀圖的值列 '{effective_y}' 必須是數值類型。")
1655
- fig = px.treemap(agg_df, path=path, values=effective_y, color=group_col if group_col else x_column, **fig_params)
1656
  elif chart_type == "雷達圖":
1657
  if not effective_y: raise ValueError("雷達圖需要 Y 軸數值或 '計數' 聚合。")
1658
  fig = go.Figure()
@@ -1675,9 +1688,11 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1675
  elif chart_type == "極座標圖":
1676
  if not effective_y: raise ValueError("極座標圖需要 Y 軸數值或 '計數' 聚合。")
1677
  if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"極座標圖的徑向值列 '{effective_y}' 必須是數值類型。")
1678
- fig = px.bar_polar(agg_df, r=effective_y, theta=x_column, color=group_col if group_col else x_column, **fig_params)
1679
  elif chart_type == "甘特圖":
1680
- start_col_gantt = y_column; end_col_gantt = group_col; task_col_gantt = x_column
 
 
1681
  if not start_col_gantt or not end_col_gantt: raise ValueError("甘特圖需要指定 開始列 (Y軸) 和 結束列 (分組列)。")
1682
  try:
1683
  df_gantt = df.copy()
@@ -1694,7 +1709,7 @@ def create_plot(df, chart_type, x_column, y_column, group_column=None, size_colu
1694
  else:
1695
  print(f"警告:未知的圖表類型 '{chart_type}',使用長條圖代替。")
1696
  if not effective_y: raise ValueError("長條圖需要 Y 軸數值或 '計數' 聚合。")
1697
- fig = px.bar(agg_df, x=x_column, y=effective_y, color=group_col, **fig_params)
1698
  # --- (繪圖邏輯結束) ---
1699
 
1700
 
@@ -1854,13 +1869,13 @@ CUSTOM_CSS = """
1854
  # =========================================
1855
  # == Gradio UI 介面定義 (Gradio UI Definition) ==
1856
  # =========================================
1857
- with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V5", theme=gr.themes.Soft()) as demo:
1858
 
1859
  # --- 應用程式標頭 ---
1860
  gr.HTML("""
1861
  <div class="app-header">
1862
- <h1 class="app-title">📊 進階數據可視化工具 V5</h1>
1863
- <p class="app-subtitle">上傳或貼上數據,創建專業圖表 (極簡化測試版)</p>
1864
  </div>
1865
  """)
1866
 
 
1273
  """
1274
  Gradio 應用程式:進階數據可視化工具
1275
  作者:Gemini
1276
+ 版本:5.1 (修正 TypeError + Y 軸加入無選項)
1277
+ 描述:包含所有功能的完整程式碼,修正繪圖函數錯誤並改進 Y 軸選項。
1278
  """
1279
 
1280
  # =========================================
 
1423
  except Exception as e: print(f"解析文本數據時發生未預期錯誤: {e}"); traceback.print_exc(); return None, f"❌ 解析數據時發生未預期錯誤: {e}"
1424
 
1425
  def update_columns_as_radio(df):
1426
+ """修改:更新列選擇為 Radio 選項,並為 Y 軸添加 '無'"""
1427
  no_data_choices = [NO_DATA_STR]
1428
  no_data_choices_with_none = [NONE_STR, NO_DATA_STR]
1429
 
 
1431
  # 返回空的 Radio 更新
1432
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1433
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1434
+ return no_data_update, no_data_update_with_none, no_data_update_with_none, no_data_update_with_none # Y 軸包含 "無"
1435
 
1436
  try:
1437
  columns = df.columns.tolist()
 
1439
  if not valid_columns: # 如果沒有有效列
1440
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1441
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1442
+ return no_data_update, no_data_update_with_none, no_data_update_with_none, no_data_update_with_none
1443
 
1444
  x_default = valid_columns[0]
1445
+ # Y 軸預設值:如果只有一列,預設選 '無';否則選第二列
1446
+ y_default = NONE_STR if len(valid_columns) <= 1 else valid_columns[1]
1447
 
1448
+ # 為 Y, group, size 添加 "無" 選項
1449
+ y_choices = [NONE_STR] + valid_columns # <--- 修改:Y 軸加入 "無"
1450
  group_choices = [NONE_STR] + valid_columns
1451
  size_choices = [NONE_STR] + valid_columns
1452
 
1453
  # 返回 Radio 更新對象
 
1454
  return (gr.Radio(choices=valid_columns, value=x_default, label="X軸 / 類別"),
1455
+ gr.Radio(choices=y_choices, value=y_default, label="Y軸 / 數值"), # <--- 修改:使用 y_choices
1456
  gr.Radio(choices=group_choices, value=NONE_STR, label="分組列"),
1457
  gr.Radio(choices=size_choices, value=NONE_STR, label="大小列"))
1458
  except Exception as e:
1459
  print(f"更新列選項 (Radio) 時出錯: {e}")
1460
  no_data_update = gr.Radio(choices=no_data_choices, value=no_data_choices[0])
1461
  no_data_update_with_none = gr.Radio(choices=no_data_choices_with_none, value=NONE_STR)
1462
+ # 返回 Y 軸包含 "無" 的更新
1463
+ return no_data_update, no_data_update_with_none, no_data_update_with_none, no_data_update_with_none
1464
 
1465
 
1466
  # =========================================
 
1476
  if isinstance(df, pd.DataFrame):
1477
  print(f" - df empty: {df.empty}", file=sys.stderr)
1478
  print(f" - df shape: {df.shape}", file=sys.stderr)
 
1479
  print(f" - chart_type: {chart_type}", file=sys.stderr)
1480
  print(f" - x_column: {x_column}", file=sys.stderr)
1481
+ print(f" - y_column: {y_column}", file=sys.stderr) # 檢查傳入的 y_column 值
1482
  print(f" - group_column: {group_column}", file=sys.stderr)
1483
  print(f" - size_column: {size_column}", file=sys.stderr)
1484
  print(f" - agg_func_name: {agg_func_name}", file=sys.stderr)
 
1492
  show_legend = True if show_legend_str == "是" else False
1493
 
1494
  # --- 1. 輸入驗證 (更嚴格) ---
1495
+ if df is None or not isinstance(df, pd.DataFrame) or df.empty:
1496
  raise ValueError("沒有有效的 DataFrame 數據可供繪圖。請先載入數據。")
1497
  if not chart_type: raise ValueError("請選擇圖表類型。")
1498
  if not agg_func_name: raise ValueError("請選擇聚合函數。")
1499
  if not x_column or x_column == NO_DATA_STR: raise ValueError("請選擇有效的 X 軸或類別列。")
1500
 
 
1501
  if x_column not in df.columns: raise ValueError(f"X 軸列 '{x_column}' 不在數據中。可用列: {', '.join(df.columns)}")
1502
 
1503
+ # --- 修改:處理 Y 軸選擇為 "無" ---
1504
+ y_col_selected = None if y_column == NONE_STR or not y_column else y_column
1505
+
1506
+ # 判斷是否需要 Y 軸
1507
  y_needed = agg_func_name != "計數" and chart_type not in ["直方圖"]
1508
  if y_needed:
1509
+ if not y_col_selected or y_col_selected == NO_DATA_STR:
1510
+ raise ValueError("此圖表類型和聚合函數需要選擇有效的 Y 軸或數值列 (不能是 '')")
1511
+ if y_col_selected not in df.columns:
1512
+ raise ValueError(f"Y 軸列 '{y_col_selected}' 不在數據中。可用列: {', '.join(df.columns)}")
1513
  else:
1514
+ y_col_selected = None # 如果不需要 Y 軸,明確設為 None
1515
 
1516
  # 處理可選列
1517
  group_col = None if group_column == NONE_STR or not group_column else group_column
 
1524
  df_processed = df.copy()
1525
 
1526
  # --- 2. 數據類型轉換與準備 ---
 
1527
  df_processed[x_column] = df_processed[x_column].astype(str)
1528
  if group_col: df_processed[group_col] = df_processed[group_col].astype(str)
1529
+ if y_col_selected: # 只在 Y 軸被選中時轉換
1530
+ try: df_processed[y_col_selected] = pd.to_numeric(df_processed[y_col_selected], errors='coerce')
1531
+ except Exception as e: print(f"警告:轉換 Y 軸列 '{y_col_selected}' 為數值時出錯: {e}")
1532
  if size_col:
1533
  try: df_processed[size_col] = pd.to_numeric(df_processed[size_col], errors='coerce')
1534
  except Exception as e: print(f"警告:轉換大小列 '{size_col}' 為數值時出錯: {e}")
1535
 
1536
  # --- 3. 數據聚合 (如果需要) ---
 
1537
  needs_aggregation = chart_type not in ["散點圖", "氣泡圖", "直方圖", "箱型圖", "小提琴圖", "甘特圖"]
1538
  agg_df = None
1539
+ y_col_agg = y_col_selected # 聚合時使用的 Y 軸列名
1540
  if needs_aggregation:
1541
  grouping_cols = [x_column] + ([group_col] if group_col else [])
1542
  invalid_grouping_cols = [col for col in grouping_cols if col not in df_processed.columns]
1543
  if invalid_grouping_cols: raise ValueError(f"以下分組/X軸列不在數據中: {', '.join(invalid_grouping_cols)}")
1544
+
1545
  if agg_func_name == "計數":
1546
  agg_df = df_processed.groupby(grouping_cols, observed=False, dropna=False).size().reset_index(name='__count__')
1547
+ y_col_agg = '__count__' # 聚合結果列名
1548
  else:
1549
  agg_func_pd = agg_function_map(agg_func_name)
1550
+ if not y_col_agg: raise ValueError(f"聚合函數 '{agg_func_name}' 需要一個有效的 Y 軸數值列。") # y_col_agg 此時就是 y_col_selected
1551
+ if agg_func_pd not in ['first', 'last'] and not pd.api.types.is_numeric_dtype(df_processed[y_col_agg]):
1552
+ try: df_processed[y_col_agg] = pd.to_numeric(df_processed[y_col_agg], errors='raise')
1553
+ except (ValueError, TypeError): raise ValueError(f"Y 軸列 '{y_col_agg}' 必須是數值類型才能執行聚合 '{agg_func_name}'。")
1554
  try:
1555
+ agg_df = df_processed.groupby(grouping_cols, observed=False, dropna=False)[y_col_agg].agg(agg_func_pd).reset_index()
1556
+ # y_col_agg 保持為選擇的 Y 軸列名
1557
  except Exception as agg_e: raise ValueError(f"執行聚合 '{agg_func_name}' 時出錯: {agg_e}")
1558
  else:
1559
  agg_df = df_processed
1560
+ y_col_agg = y_col_selected # 使用選擇的 Y 軸列名 (可能為 None)
1561
 
1562
  if agg_df is None or agg_df.empty: raise ValueError("數據聚合後沒有產生有效結果。")
1563
  required_cols_for_plot = [x_column]
1564
+ # 修正:只有在 y_col_agg 實際有值時才檢查
1565
  if y_col_agg: required_cols_for_plot.append(y_col_agg)
1566
  if group_col: required_cols_for_plot.append(group_col)
1567
  if size_col: required_cols_for_plot.append(size_col)
 
1574
  # --- 5. 創建圖表 (核心邏輯) ---
1575
  fig_params = {"data_frame": agg_df, "title": title, "color_discrete_sequence": colors, "width": width, "height": height}
1576
  if group_col and custom_colors_dict: fig_params["color_discrete_map"] = custom_colors_dict
1577
+
1578
+ # 修正:effective_y 應該基於 y_col_agg (聚合結果列 或 選擇的 Y 軸列)
1579
  effective_y = y_col_agg if y_needed or agg_func_name == "計數" else None
1580
 
1581
+ # --- (繪圖邏輯開始) ---
1582
+ # --- 修正 px.pie 和 px.imshow 的調用 ---
1583
+ if chart_type == "圓餅圖":
1584
+ if not effective_y: raise ValueError("圓餅圖需要 Y 軸數值或 '計數' 聚合。")
1585
+ if group_col: print("警告:圓餅圖不支持分組列,已忽略。")
1586
+ # 修正:移除第一個 positional argument agg_df
1587
+ fig = px.pie(names=x_column, values=effective_y, **fig_params)
1588
+ if not group_col and custom_colors_dict: fig.update_traces(marker=dict(colors=[custom_colors_dict.get(str(cat), colors[i % len(colors)]) for i, cat in enumerate(agg_df[x_column])]))
1589
+ elif chart_type == "環形圖":
1590
+ if not effective_y: raise ValueError("環形圖需要 Y 軸數值或 '計數' 聚合。")
1591
+ if group_col: print("警告:環形圖不支持分組列,已忽略。")
1592
+ # 修正:移除第一個 positional argument agg_df
1593
+ fig = px.pie(names=x_column, values=effective_y, hole=0.4, **fig_params)
1594
+ if not group_col and custom_colors_dict: fig.update_traces(marker=dict(colors=[custom_colors_dict.get(str(cat), colors[i % len(colors)]) for i, cat in enumerate(agg_df[x_column])]))
1595
+ elif chart_type == "熱力圖":
1596
+ if not effective_y: raise ValueError("熱力圖需要 Y 軸數值或 '計數' 聚合。")
1597
+ if not group_col: raise ValueError("熱力圖需要 X 軸、Y 軸 和一個 分組列。")
1598
+ try:
1599
+ if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"熱力圖的值列 '{effective_y}' 必須是數值類型。")
1600
+ pivot_df = pd.pivot_table(agg_df, values=effective_y, index=group_col, columns=x_column, aggfunc=agg_function_map(agg_func_name) if agg_func_name != "計數" else 'size')
1601
+ # 修正:為 imshow 創建不含 data_frame 的參數字典
1602
+ fig_params_imshow = fig_params.copy()
1603
+ fig_params_imshow.pop('data_frame', None) # 移除 data_frame
1604
+ fig = px.imshow(pivot_df, color_continuous_scale=px.colors.sequential.Viridis, aspect="auto", text_auto=True, **fig_params_imshow); # 使用修改後的參數
1605
+ fig.update_layout(coloraxis_showscale=True)
1606
+ except Exception as pivot_e: raise ValueError(f"創建熱力圖的數據透視表時出錯: {pivot_e}")
1607
+ # --- 其他圖表類型 (與 V4/V5 邏輯相同,確保 effective_y 使用正確) ---
1608
+ elif chart_type == "長條圖":
1609
  if not effective_y: raise ValueError("長條圖需要 Y 軸數值或 '計數' 聚合。")
1610
+ fig = px.bar(x=x_column, y=effective_y, color=group_col, **fig_params)
1611
  elif chart_type == "堆疊長條圖":
1612
  if not effective_y: raise ValueError("堆疊長條圖需要 Y 軸數值或 '計數' 聚合。")
1613
+ fig = px.bar(x=x_column, y=effective_y, color=group_col, barmode='stack', **fig_params)
1614
  elif chart_type == "百分比堆疊長條圖":
1615
  if not effective_y: raise ValueError("百分比堆疊長條圖需要 Y 軸數值或 '計數' 聚合。")
1616
+ fig = px.bar(x=x_column, y=effective_y, color=group_col, barmode='relative', text_auto='.1%', **fig_params)
1617
  fig.update_layout(yaxis_title="百分比 (%)")
1618
  elif chart_type == "群組長條圖":
1619
  if not effective_y: raise ValueError("群組長條圖需要 Y 軸數值或 '計數' 聚合。")
1620
+ fig = px.bar(x=x_column, y=effective_y, color=group_col, barmode='group', **fig_params)
1621
  elif chart_type == "水平長條圖":
1622
  if not effective_y: raise ValueError("水平長條圖需要 Y 軸數值或 '計數' 聚合。")
1623
+ fig = px.bar(y=x_column, x=effective_y, color=group_col, orientation='h', **fig_params)
1624
  elif chart_type == "折線圖":
1625
  if not effective_y: raise ValueError("折線圖需要 Y 軸數值或 '計數' 聚合。")
1626
+ fig = px.line(x=x_column, y=effective_y, color=group_col, markers=True, **fig_params)
1627
  elif chart_type == "多重折線圖":
1628
  if not effective_y: raise ValueError("多重折線圖需要 Y 軸數值或 '計數' 聚合。")
1629
+ fig = px.line(x=x_column, y=effective_y, color=group_col, markers=True, **fig_params)
1630
  elif chart_type == "階梯折線圖":
1631
  if not effective_y: raise ValueError("階梯折線圖需要 Y 軸數值或 '計數' 聚合。")
1632
+ fig = px.line(x=x_column, y=effective_y, color=group_col, line_shape='hv', **fig_params)
1633
  elif chart_type == "區域圖":
1634
  if not effective_y: raise ValueError("區域圖需要 Y 軸數值或 '計數' 聚合。")
1635
+ fig = px.area(x=x_column, y=effective_y, color=group_col, **fig_params)
1636
  elif chart_type == "堆疊區域圖":
1637
  if not effective_y: raise ValueError("堆疊區域圖需要 Y 軸數值或 '計數' 聚合。")
1638
+ fig = px.area(x=x_column, y=effective_y, color=group_col, groupnorm=None, **fig_params)
1639
  elif chart_type == "百分比堆疊區域圖":
1640
  if not effective_y: raise ValueError("百分比堆疊區域圖需要 Y 軸數值或 '計數' 聚合。")
1641
+ fig = px.area(x=x_column, y=effective_y, color=group_col, groupnorm='percent', **fig_params)
1642
  fig.update_layout(yaxis_title="百分比 (%)")
 
 
 
 
 
 
 
 
 
 
1643
  elif chart_type == "散點圖":
1644
+ if not y_col_selected: raise ValueError("散點圖需要選擇 Y 軸列。") # 使用 y_col_selected
1645
+ fig = px.scatter(x=x_column, y=y_col_selected, color=group_col, size=size_col, **fig_params)
1646
  elif chart_type == "氣泡圖":
1647
+ if not y_col_selected: raise ValueError("氣泡圖需要選擇 Y 軸列。")
1648
  if not size_col: raise ValueError("氣泡圖需要指定 '大小列'。")
1649
  if not pd.api.types.is_numeric_dtype(agg_df[size_col]): raise ValueError(f"大小列 '{size_col}' 必須是數值類型。")
1650
+ fig = px.scatter(x=x_column, y=y_col_selected, color=group_col, size=size_col, size_max=60, **fig_params)
1651
  elif chart_type == "直方圖":
1652
  if not pd.api.types.is_numeric_dtype(agg_df[x_column]): raise ValueError(f"直方圖的 X 軸列 '{x_column}' 必須是數值類型。")
1653
+ fig = px.histogram(x=x_column, color=group_col, **fig_params); fig.update_layout(yaxis_title="計數")
1654
  elif chart_type == "箱型圖":
1655
+ if not y_col_selected: raise ValueError("箱型圖需要選擇 Y 軸列。")
1656
+ if not pd.api.types.is_numeric_dtype(agg_df[y_col_selected]): raise ValueError(f"箱型圖的 Y 軸列 '{y_col_selected}' 必須是數值類型。")
1657
+ fig = px.box(x=group_col, y=y_col_selected, color=group_col, **fig_params)
1658
+ if not group_col: fig = px.box(y=y_col_selected, **fig_params)
1659
  elif chart_type == "小提琴圖":
1660
+ if not y_col_selected: raise ValueError("小提琴圖需要選擇 Y 軸列。")
1661
+ if not pd.api.types.is_numeric_dtype(agg_df[y_col_selected]): raise ValueError(f"小提琴圖的 Y 軸列 '{y_col_selected}' 必須是數值類型。")
1662
+ fig = px.violin(x=group_col, y=y_col_selected, color=group_col, box=True, points="all", **fig_params)
1663
+ if not group_col: fig = px.violin(y=y_col_selected, box=True, points="all", **fig_params)
 
 
 
 
 
 
 
 
 
1664
  elif chart_type == "樹狀圖":
1665
  if not effective_y: raise ValueError("樹狀圖需要 Y 軸數值或 '計數' 聚合。")
1666
  path = [group_col, x_column] if group_col else [x_column]
1667
  if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"樹狀圖的值列 '{effective_y}' 必須是數值類型。")
1668
+ fig = px.treemap(path=path, values=effective_y, color=group_col if group_col else x_column, **fig_params)
1669
  elif chart_type == "雷達圖":
1670
  if not effective_y: raise ValueError("雷達圖需要 Y 軸數值或 '計數' 聚合。")
1671
  fig = go.Figure()
 
1688
  elif chart_type == "極座標圖":
1689
  if not effective_y: raise ValueError("極座標圖需要 Y 軸數值或 '計數' 聚合。")
1690
  if not pd.api.types.is_numeric_dtype(agg_df[effective_y]): raise ValueError(f"極座標圖的徑向值列 '{effective_y}' 必須是數值類型。")
1691
+ fig = px.bar_polar(r=effective_y, theta=x_column, color=group_col if group_col else x_column, **fig_params)
1692
  elif chart_type == "甘特圖":
1693
+ start_col_gantt = y_col_selected # Y 軸被用作開始列
1694
+ end_col_gantt = group_col # 分組列被用作結束列
1695
+ task_col_gantt = x_column # X 軸被用作任務列
1696
  if not start_col_gantt or not end_col_gantt: raise ValueError("甘特圖需要指定 開始列 (Y軸) 和 結束列 (分組列)。")
1697
  try:
1698
  df_gantt = df.copy()
 
1709
  else:
1710
  print(f"警告:未知的圖表類型 '{chart_type}',使用長條圖代替。")
1711
  if not effective_y: raise ValueError("長條圖需要 Y 軸數值或 '計數' 聚合。")
1712
+ fig = px.bar(x=x_column, y=effective_y, color=group_col, **fig_params)
1713
  # --- (繪圖邏輯結束) ---
1714
 
1715
 
 
1869
  # =========================================
1870
  # == Gradio UI 介面定義 (Gradio UI Definition) ==
1871
  # =========================================
1872
+ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V5.1", theme=gr.themes.Soft()) as demo:
1873
 
1874
  # --- 應用程式標頭 ---
1875
  gr.HTML("""
1876
  <div class="app-header">
1877
+ <h1 class="app-title">📊 進階數據可視化工具 V5.1</h1>
1878
+ <p class="app-subtitle">上傳或貼上數據,創建專業圖表 (極簡化測試版 - 修正)</p>
1879
  </div>
1880
  """)
1881