Update app.py
Browse files
app.py
CHANGED
|
@@ -1273,7 +1273,7 @@ def recommend_chart_settings(df):
|
|
| 1273 |
"""
|
| 1274 |
Gradio 應用程式:進階數據可視化工具
|
| 1275 |
作者:Gemini
|
| 1276 |
-
版本:1.
|
| 1277 |
描述:此部分包含 Gradio UI 介面定義、事件處理和應用程式啟動。
|
| 1278 |
"""
|
| 1279 |
|
|
@@ -1329,7 +1329,8 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1329 |
gr.HTML('<div class="section-title">2. 數據預覽與導出</div>')
|
| 1330 |
with gr.Group(elem_classes=["card"]):
|
| 1331 |
gr.Markdown("下方將顯示載入或解析後的數據預覽。")
|
| 1332 |
-
|
|
|
|
| 1333 |
|
| 1334 |
with gr.Row():
|
| 1335 |
export_format = gr.Dropdown(
|
|
@@ -1535,6 +1536,13 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1535 |
preview_df = df if df is not None else pd.DataFrame()
|
| 1536 |
# 更新所有列選擇下拉列表
|
| 1537 |
col_updates = update_columns(df) # 返回 4 個 Dropdown 更新對象
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1538 |
return [df, status_msg, preview_df] + list(col_updates) * 2 # 更新兩組下拉列表
|
| 1539 |
|
| 1540 |
upload_button.click(
|
|
@@ -1600,15 +1608,26 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1600 |
outputs=[chart_output_1]
|
| 1601 |
)
|
| 1602 |
# 當任何相關設置改變時,自動更新圖表一
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
for input_component in chart_inputs_1:
|
| 1604 |
# 避免狀態變量觸發無限循環,只監聽 UI 組件的變化
|
| 1605 |
-
if isinstance(input_component,
|
| 1606 |
input_component.change(
|
| 1607 |
-
|
| 1608 |
-
inputs=chart_inputs_1,
|
| 1609 |
outputs=[chart_output_1]
|
| 1610 |
)
|
| 1611 |
|
|
|
|
| 1612 |
# --- 圖表一:導出圖表 ---
|
| 1613 |
download_button_1.click(
|
| 1614 |
download_figure,
|
|
@@ -1620,30 +1639,41 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1620 |
def apply_recommendation(rec_dict):
|
| 1621 |
"""將推薦字典應用到圖表一的 UI 組件"""
|
| 1622 |
if not isinstance(rec_dict, dict):
|
| 1623 |
-
|
| 1624 |
-
|
| 1625 |
-
|
| 1626 |
-
|
| 1627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1628 |
return [
|
| 1629 |
-
gr.Dropdown(value=
|
| 1630 |
-
gr.Dropdown(value=
|
| 1631 |
-
gr.Dropdown(value=
|
| 1632 |
-
gr.Dropdown(value=
|
| 1633 |
-
gr.Dropdown(value=
|
| 1634 |
]
|
| 1635 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1636 |
recommend_button_1.click(
|
| 1637 |
recommend_chart_settings,
|
| 1638 |
inputs=[data_state],
|
| 1639 |
-
outputs=
|
| 1640 |
).then(
|
| 1641 |
apply_recommendation,
|
| 1642 |
-
inputs=
|
| 1643 |
outputs=[chart_type_1, x_column_1, y_column_1, group_column_1, agg_function_1]
|
| 1644 |
).then( # 應用推薦後立即觸發一次圖表更新
|
| 1645 |
-
create_plot,
|
| 1646 |
-
inputs=chart_inputs_1, #
|
| 1647 |
outputs=[chart_output_1]
|
| 1648 |
)
|
| 1649 |
|
|
@@ -1675,10 +1705,13 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1675 |
outputs=[chart_output_2]
|
| 1676 |
)
|
| 1677 |
# 當任何相關設置改變時,自動更新圖表二
|
|
|
|
|
|
|
|
|
|
| 1678 |
for input_component in chart_inputs_2:
|
| 1679 |
-
if isinstance(input_component,
|
| 1680 |
input_component.change(
|
| 1681 |
-
|
| 1682 |
inputs=chart_inputs_2,
|
| 1683 |
outputs=[chart_output_2]
|
| 1684 |
)
|
|
@@ -1693,69 +1726,75 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1693 |
# --- 圖表類型改變時更新 UI 元素可見性 (針對兩個圖表) ---
|
| 1694 |
def update_element_visibility(chart_type):
|
| 1695 |
"""根據圖表類型更新 Y 軸、分組列、大小列的標籤和可見性"""
|
| 1696 |
-
#
|
| 1697 |
-
|
| 1698 |
-
|
| 1699 |
-
|
| 1700 |
-
|
| 1701 |
-
|
| 1702 |
-
|
| 1703 |
-
|
| 1704 |
-
|
| 1705 |
-
|
| 1706 |
-
|
| 1707 |
-
|
| 1708 |
-
|
| 1709 |
-
|
| 1710 |
-
|
| 1711 |
-
|
| 1712 |
-
|
| 1713 |
-
|
| 1714 |
-
|
| 1715 |
-
|
| 1716 |
-
|
| 1717 |
-
|
| 1718 |
-
|
| 1719 |
-
|
| 1720 |
-
|
| 1721 |
-
|
| 1722 |
-
|
| 1723 |
-
|
| 1724 |
-
|
| 1725 |
-
|
| 1726 |
-
|
| 1727 |
-
|
| 1728 |
-
|
| 1729 |
-
|
| 1730 |
-
|
| 1731 |
-
|
| 1732 |
-
|
| 1733 |
-
|
| 1734 |
-
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
-
|
| 1739 |
-
|
| 1740 |
-
|
| 1741 |
-
|
| 1742 |
-
|
| 1743 |
-
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
| 1747 |
-
|
| 1748 |
-
|
| 1749 |
-
|
| 1750 |
-
|
| 1751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1752 |
|
| 1753 |
-
# 返回更新對象
|
| 1754 |
-
return (
|
| 1755 |
-
gr.update(label=y_label, visible=y_needed),
|
| 1756 |
-
gr.update(label=group_label, visible=group_needed),
|
| 1757 |
-
gr.update(label=size_label, visible=size_needed)
|
| 1758 |
-
)
|
| 1759 |
|
| 1760 |
# 將 chart_type 的變化連接到更新函數,並應用到兩個圖表的對應組件
|
| 1761 |
chart_type_1.change(
|
|
@@ -1775,4 +1814,6 @@ with gr.Blocks(css=CUSTOM_CSS, title="進階數據可視化工具 V2", theme=gr.
|
|
| 1775 |
if __name__ == "__main__":
|
| 1776 |
# 在 Hugging Face Spaces 上,通常不需要 share=True
|
| 1777 |
# debug=True 可以在開發時提供更詳細的錯誤信息
|
| 1778 |
-
|
|
|
|
|
|
|
|
|
| 1273 |
"""
|
| 1274 |
Gradio 應用程式:進階數據可視化工具
|
| 1275 |
作者:Gemini
|
| 1276 |
+
版本:1.1 (修正 Dataframe height 參數錯誤)
|
| 1277 |
描述:此部分包含 Gradio UI 介面定義、事件處理和應用程式啟動。
|
| 1278 |
"""
|
| 1279 |
|
|
|
|
| 1329 |
gr.HTML('<div class="section-title">2. 數據預覽與導出</div>')
|
| 1330 |
with gr.Group(elem_classes=["card"]):
|
| 1331 |
gr.Markdown("下方將顯示載入或解析後的數據預覽。")
|
| 1332 |
+
# 修正:移除 height 參數
|
| 1333 |
+
data_preview = gr.Dataframe(label="數據表格預覽", interactive=False)
|
| 1334 |
|
| 1335 |
with gr.Row():
|
| 1336 |
export_format = gr.Dropdown(
|
|
|
|
| 1536 |
preview_df = df if df is not None else pd.DataFrame()
|
| 1537 |
# 更新所有列選擇下拉列表
|
| 1538 |
col_updates = update_columns(df) # 返回 4 個 Dropdown 更新對象
|
| 1539 |
+
# 檢查 col_updates 是否為 None 或空,避免解包錯誤
|
| 1540 |
+
if col_updates is None or len(col_updates) != 4:
|
| 1541 |
+
# 如果更新失敗,返回原始狀態或空更新
|
| 1542 |
+
print("警告: update_columns 未返回預期的 4 個組件更新。")
|
| 1543 |
+
# 返回空更新避免錯誤,但下拉列表可能不會更新
|
| 1544 |
+
return [df, status_msg, preview_df] + [gr.update()] * 8
|
| 1545 |
+
|
| 1546 |
return [df, status_msg, preview_df] + list(col_updates) * 2 # 更新兩組下拉列表
|
| 1547 |
|
| 1548 |
upload_button.click(
|
|
|
|
| 1608 |
outputs=[chart_output_1]
|
| 1609 |
)
|
| 1610 |
# 當任何相關設置改變時,自動更新圖表一
|
| 1611 |
+
# 將 .change 事件綁定移到循環外,以避免在循環內部重複定義函數
|
| 1612 |
+
def auto_update_chart_1(*inputs):
|
| 1613 |
+
# inputs 是一個包含所有 chart_inputs_1 值的元組
|
| 1614 |
+
# 需要將這些值解包傳遞給 create_plot
|
| 1615 |
+
# 注意:Gradio 的 .change 會將所有 input 組件的當前值按順序傳遞
|
| 1616 |
+
# 我們需要確保這個順序與 create_plot 的參數順序匹配
|
| 1617 |
+
# 或者,更安全的方式是從 inputs 列表創建一個字典
|
| 1618 |
+
# 但這裡直接按順序傳遞應該可行,因為 inputs 列表順序固定
|
| 1619 |
+
return create_plot(*inputs)
|
| 1620 |
+
|
| 1621 |
for input_component in chart_inputs_1:
|
| 1622 |
# 避免狀態變量觸發無限循環,只監聽 UI 組件的變化
|
| 1623 |
+
if not isinstance(input_component, gr.State): # 排除 State 組件
|
| 1624 |
input_component.change(
|
| 1625 |
+
auto_update_chart_1, # 使用輔助函數
|
| 1626 |
+
inputs=chart_inputs_1, # 傳遞所有需要的輸入
|
| 1627 |
outputs=[chart_output_1]
|
| 1628 |
)
|
| 1629 |
|
| 1630 |
+
|
| 1631 |
# --- 圖表一:導出圖表 ---
|
| 1632 |
download_button_1.click(
|
| 1633 |
download_figure,
|
|
|
|
| 1639 |
def apply_recommendation(rec_dict):
|
| 1640 |
"""將推薦字典應用到圖表一的 UI 組件"""
|
| 1641 |
if not isinstance(rec_dict, dict):
|
| 1642 |
+
# 如果輸入不是字典,返回空更新列表
|
| 1643 |
+
print("警告:apply_recommendation 收到非字典輸入。")
|
| 1644 |
+
return [gr.update()] * 5
|
| 1645 |
+
|
| 1646 |
+
# 安全地獲取推薦值,提供默認值
|
| 1647 |
+
chart_type_val = rec_dict.get("chart_type")
|
| 1648 |
+
x_col_val = rec_dict.get("x_column")
|
| 1649 |
+
agg_func_val = rec_dict.get("agg_function")
|
| 1650 |
+
y_col_val = None if agg_func_val == "計數" else rec_dict.get("y_column")
|
| 1651 |
+
group_col_val = rec_dict.get("group_column", "無") # 默認為 "無"
|
| 1652 |
+
|
| 1653 |
+
# 返回更新列表
|
| 1654 |
return [
|
| 1655 |
+
gr.Dropdown(value=chart_type_val),
|
| 1656 |
+
gr.Dropdown(value=x_col_val),
|
| 1657 |
+
gr.Dropdown(value=y_col_val),
|
| 1658 |
+
gr.Dropdown(value=group_col_val),
|
| 1659 |
+
gr.Dropdown(value=agg_func_val)
|
| 1660 |
]
|
| 1661 |
|
| 1662 |
+
|
| 1663 |
+
# 創建一個狀態來存儲推薦結果,以便在 then() 中使用
|
| 1664 |
+
recommendation_state = gr.State({})
|
| 1665 |
+
|
| 1666 |
recommend_button_1.click(
|
| 1667 |
recommend_chart_settings,
|
| 1668 |
inputs=[data_state],
|
| 1669 |
+
outputs=[recommendation_state] # 將結果存儲在狀態中
|
| 1670 |
).then(
|
| 1671 |
apply_recommendation,
|
| 1672 |
+
inputs=[recommendation_state], # 從狀態讀取推薦結果
|
| 1673 |
outputs=[chart_type_1, x_column_1, y_column_1, group_column_1, agg_function_1]
|
| 1674 |
).then( # 應用推薦後立即觸發一次圖表更新
|
| 1675 |
+
create_plot, # 直接調用 create_plot
|
| 1676 |
+
inputs=chart_inputs_1, # 再次收集所有當前輸入
|
| 1677 |
outputs=[chart_output_1]
|
| 1678 |
)
|
| 1679 |
|
|
|
|
| 1705 |
outputs=[chart_output_2]
|
| 1706 |
)
|
| 1707 |
# 當任何相關設置改變時,自動更新圖表二
|
| 1708 |
+
def auto_update_chart_2(*inputs):
|
| 1709 |
+
return create_plot(*inputs)
|
| 1710 |
+
|
| 1711 |
for input_component in chart_inputs_2:
|
| 1712 |
+
if not isinstance(input_component, gr.State):
|
| 1713 |
input_component.change(
|
| 1714 |
+
auto_update_chart_2,
|
| 1715 |
inputs=chart_inputs_2,
|
| 1716 |
outputs=[chart_output_2]
|
| 1717 |
)
|
|
|
|
| 1726 |
# --- 圖表類型改變時更新 UI 元素可見性 (針對兩個圖表) ---
|
| 1727 |
def update_element_visibility(chart_type):
|
| 1728 |
"""根據圖表類型更新 Y 軸、分組列、大小列的標籤和可見性"""
|
| 1729 |
+
try: # 添加 try-except 塊以捕獲潛在錯誤
|
| 1730 |
+
# 圓餅圖、環形圖、漏斗圖、樹狀圖:主要關心類別 (X) 和數值 (Y)
|
| 1731 |
+
is_pie_like = chart_type in ["圓餅圖", "環形圖", "漏斗圖", "樹狀圖"]
|
| 1732 |
+
# 直方圖:主要關心 X 軸分佈,Y 軸是計數
|
| 1733 |
+
is_histogram = chart_type == "直方圖"
|
| 1734 |
+
# 箱型圖、小提琴圖:Y 軸是數值,X 軸是可選的分組
|
| 1735 |
+
is_box_violin = chart_type in ["箱型圖", "小提琴圖"]
|
| 1736 |
+
# 甘特圖:Y 軸是開始時間,分組列是結束時間
|
| 1737 |
+
is_gantt = chart_type == "甘特圖"
|
| 1738 |
+
# 熱力圖:需要 X, Y 和分組列
|
| 1739 |
+
is_heatmap = chart_type == "熱力圖"
|
| 1740 |
+
# 雷達圖:Theta (X), R (Y), Color (Group)
|
| 1741 |
+
is_radar = chart_type == "雷達圖"
|
| 1742 |
+
|
| 1743 |
+
# Y 軸的標籤和需求
|
| 1744 |
+
y_label = "Y軸 / 數值"
|
| 1745 |
+
y_needed = True
|
| 1746 |
+
if is_histogram:
|
| 1747 |
+
y_label = "Y軸 (自動計數)"
|
| 1748 |
+
y_needed = False # 不需要用戶選擇 Y
|
| 1749 |
+
elif is_pie_like:
|
| 1750 |
+
y_label = "數值列 (用於大小/值)"
|
| 1751 |
+
elif is_box_violin:
|
| 1752 |
+
y_label = "數值列"
|
| 1753 |
+
elif is_gantt:
|
| 1754 |
+
y_label = "開始時間列"
|
| 1755 |
+
elif is_radar:
|
| 1756 |
+
y_label = "徑向值 (R)"
|
| 1757 |
+
|
| 1758 |
+
|
| 1759 |
+
# 分組列的標籤和需求
|
| 1760 |
+
group_label = "分組列"
|
| 1761 |
+
group_needed = chart_type in [
|
| 1762 |
+
"堆疊長條圖", "百分比堆疊長條圖", "群組長條圖", "水平長條圖", # 可選
|
| 1763 |
+
"折線圖", "多重折線圖", "階梯折線圖", # 可選
|
| 1764 |
+
"區域圖", "堆疊區域圖", "百分比堆疊區域圖", # 可選
|
| 1765 |
+
"散點圖", "氣泡圖", # 可選
|
| 1766 |
+
"箱型圖", "小提琴圖", # 可選,用於 X 軸
|
| 1767 |
+
"熱力圖", # 必需,用於 Index 或 Columns
|
| 1768 |
+
"雷達圖", # 必需,用於區分線條
|
| 1769 |
+
"極座標圖" # 可選
|
| 1770 |
+
]
|
| 1771 |
+
if is_gantt:
|
| 1772 |
+
group_label = "結束時間列"
|
| 1773 |
+
group_needed = True
|
| 1774 |
+
elif is_heatmap:
|
| 1775 |
+
group_label = "行/列 分組"
|
| 1776 |
+
group_needed = True
|
| 1777 |
+
|
| 1778 |
+
|
| 1779 |
+
# 大小列的需求
|
| 1780 |
+
size_label = "大小列"
|
| 1781 |
+
size_needed = chart_type in ["氣泡圖", "散點圖"] # 甘特圖顏色也可以用 Size 列
|
| 1782 |
+
if is_gantt:
|
| 1783 |
+
size_label = "顏色列 (可選)"
|
| 1784 |
+
size_needed = True # 允許選擇顏色列
|
| 1785 |
+
|
| 1786 |
+
|
| 1787 |
+
# 返回更新對象
|
| 1788 |
+
return (
|
| 1789 |
+
gr.update(label=y_label, visible=y_needed),
|
| 1790 |
+
gr.update(label=group_label, visible=group_needed),
|
| 1791 |
+
gr.update(label=size_label, visible=size_needed)
|
| 1792 |
+
)
|
| 1793 |
+
except Exception as e:
|
| 1794 |
+
print(f"Error in update_element_visibility: {e}")
|
| 1795 |
+
# 返回默認更新以避免應用程序崩潰
|
| 1796 |
+
return (gr.update(), gr.update(), gr.update())
|
| 1797 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1798 |
|
| 1799 |
# 將 chart_type 的變化連接到更新函數,並應用到兩個圖表的對應組件
|
| 1800 |
chart_type_1.change(
|
|
|
|
| 1814 |
if __name__ == "__main__":
|
| 1815 |
# 在 Hugging Face Spaces 上,通常不需要 share=True
|
| 1816 |
# debug=True 可以在開發時提供更詳細的錯誤信息
|
| 1817 |
+
# 增加 server_port 以便在本地運行時指定端口,避免衝突
|
| 1818 |
+
# demo.launch(debug=True, server_port=7860)
|
| 1819 |
+
demo.launch(debug=True) # 保持原樣,Gradio 會自動選擇端口
|