Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -31,22 +31,21 @@ def make_demo_dataframe() -> pd.DataFrame:
|
|
| 31 |
def load_csv(file: gr.File | None) -> pd.DataFrame:
|
| 32 |
"""讀 CSV(需含時間欄);若未上傳則回傳示範資料。統一轉為 Asia/Taipei 並依時間排序。"""
|
| 33 |
if file is None:
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
df = pd.read_csv(file.name)
|
| 37 |
-
time_col_candidates = ["time", "timestamp", "datetime", "date"]
|
| 38 |
-
time_col = next((c for c in time_col_candidates if c in df.columns), None)
|
| 39 |
-
if time_col is None:
|
| 40 |
-
raise ValueError("CSV 檔需包含時間欄位,例如:time / timestamp / datetime / date")
|
| 41 |
-
|
| 42 |
-
df[time_col] = pd.to_datetime(df[time_col])
|
| 43 |
-
df.rename(columns={time_col: "time"}, inplace=True)
|
| 44 |
-
|
| 45 |
-
# 統一時區
|
| 46 |
-
if getattr(df["time"].dt, "tz", None) is None:
|
| 47 |
-
df["time"] = df["time"].dt.tz_localize(TAIPEI)
|
| 48 |
else:
|
| 49 |
-
df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
df = df.sort_values("time").reset_index(drop=True)
|
| 52 |
return df
|
|
@@ -83,7 +82,6 @@ def build_grafanalib_dashboard(series_columns: list[str], dual_axis: bool, rolli
|
|
| 83 |
title = f"Event Count (Bar) - {series_columns[1]}"
|
| 84 |
|
| 85 |
if dual_axis:
|
| 86 |
-
# 在 JSON 中同面板加上第一欄(折線);實際右軸指定在 Grafana 端做 series override
|
| 87 |
targets.append(Target(expr=f"{series_columns[0]}", legendFormat=f"{series_columns[0]} (line)"))
|
| 88 |
lines = True
|
| 89 |
bars = True
|
|
@@ -147,10 +145,10 @@ def render_bar_or_dual(df: pd.DataFrame, second_col: str, first_col: str, dual_a
|
|
| 147 |
ax2.set_ylabel(first_col)
|
| 148 |
title = f"Bar+Line (Dual Axis) - {second_col} / {first_col}"
|
| 149 |
|
| 150 |
-
#
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
ax.legend(
|
| 154 |
else:
|
| 155 |
ax.legend(loc="upper left")
|
| 156 |
|
|
@@ -179,11 +177,10 @@ def render_rolling(df: pd.DataFrame, col: str, window: int = 5):
|
|
| 179 |
# -----------------------------
|
| 180 |
def pipeline(file, series_choice, dual_axis, rolling_window):
|
| 181 |
"""
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
4) 回傳三張圖與下載檔(以「路徑」回傳)
|
| 187 |
"""
|
| 188 |
df = load_csv(file)
|
| 189 |
|
|
@@ -206,7 +203,7 @@ def pipeline(file, series_choice, dual_axis, rolling_window):
|
|
| 206 |
f.write(dash_json_str)
|
| 207 |
json_path = f.name
|
| 208 |
|
| 209 |
-
#
|
| 210 |
fig1 = render_line(df, chosen[0])
|
| 211 |
if len(chosen) > 1:
|
| 212 |
fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis))
|
|
@@ -218,7 +215,13 @@ def pipeline(file, series_choice, dual_axis, rolling_window):
|
|
| 218 |
|
| 219 |
fig3, df_with_roll = render_rolling(df.copy(), chosen[0], window=int(rolling_window))
|
| 220 |
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
|
| 224 |
# -----------------------------
|
|
@@ -226,11 +229,11 @@ def pipeline(file, series_choice, dual_axis, rolling_window):
|
|
| 226 |
# -----------------------------
|
| 227 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 228 |
gr.Markdown(
|
| 229 |
-
"
|
| 230 |
"- 圖1:折線(第一個數值欄)\n"
|
| 231 |
-
"- 圖2
|
| 232 |
-
"- 圖3:第一個欄位的 Rolling Mean
|
| 233 |
-
"
|
| 234 |
)
|
| 235 |
|
| 236 |
with gr.Row():
|
|
@@ -251,9 +254,10 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 251 |
plot2 = gr.Plot(label="圖 2:Bar / Dual Axis")
|
| 252 |
plot3 = gr.Plot(label="圖 3:Rolling Mean")
|
| 253 |
|
| 254 |
-
json_box = gr.Code(label="grafanalib Dashboard JSON
|
| 255 |
json_file = gr.File(label="下載 dashboard.json")
|
| 256 |
-
|
|
|
|
| 257 |
|
| 258 |
# 依檔案動態更新欄位選單與表格
|
| 259 |
def probe_columns(file):
|
|
@@ -262,23 +266,26 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 262 |
default_select = numeric_cols[:2]
|
| 263 |
return gr.CheckboxGroup(choices=numeric_cols, value=default_select), df
|
| 264 |
|
| 265 |
-
# 初次載入:以 Demo
|
| 266 |
def initial_load():
|
| 267 |
-
f1, f2, f3, dash_json, json_path, df = pipeline(
|
| 268 |
file=None, series_choice=[], dual_axis=False, rolling_window="5"
|
| 269 |
)
|
| 270 |
numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
|
| 271 |
default_select = numeric_cols[:2]
|
| 272 |
return (
|
| 273 |
-
f1, f2, f3,
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
| 276 |
)
|
| 277 |
|
| 278 |
demo.load(
|
| 279 |
initial_load,
|
| 280 |
inputs=None,
|
| 281 |
-
outputs=[plot1, plot2, plot3, json_box, json_file, df_view, series_multiselect, dual_axis_chk, rolling_dd],
|
| 282 |
)
|
| 283 |
|
| 284 |
file_in.change(
|
|
@@ -290,7 +297,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
| 290 |
run_btn.click(
|
| 291 |
pipeline,
|
| 292 |
inputs=[file_in, series_multiselect, dual_axis_chk, rolling_dd],
|
| 293 |
-
outputs=[plot1, plot2, plot3, json_box, json_file, df_view],
|
| 294 |
)
|
| 295 |
|
| 296 |
if __name__ == "__main__":
|
|
|
|
| 31 |
def load_csv(file: gr.File | None) -> pd.DataFrame:
|
| 32 |
"""讀 CSV(需含時間欄);若未上傳則回傳示範資料。統一轉為 Asia/Taipei 並依時間排序。"""
|
| 33 |
if file is None:
|
| 34 |
+
df = make_demo_dataframe()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
else:
|
| 36 |
+
df = pd.read_csv(file.name)
|
| 37 |
+
time_col_candidates = ["time", "timestamp", "datetime", "date"]
|
| 38 |
+
time_col = next((c for c in time_col_candidates if c in df.columns), None)
|
| 39 |
+
if time_col is None:
|
| 40 |
+
raise ValueError("CSV 檔需包含時間欄位,例如:time / timestamp / datetime / date")
|
| 41 |
+
df[time_col] = pd.to_datetime(df[time_col])
|
| 42 |
+
df.rename(columns={time_col: "time"}, inplace=True)
|
| 43 |
+
|
| 44 |
+
# 統一時區
|
| 45 |
+
if getattr(df["time"].dt, "tz", None) is None:
|
| 46 |
+
df["time"] = df["time"].dt.tz_localize(TAIPEI)
|
| 47 |
+
else:
|
| 48 |
+
df["time"] = df["time"].dt.tz_convert(TAIPEI)
|
| 49 |
|
| 50 |
df = df.sort_values("time").reset_index(drop=True)
|
| 51 |
return df
|
|
|
|
| 82 |
title = f"Event Count (Bar) - {series_columns[1]}"
|
| 83 |
|
| 84 |
if dual_axis:
|
|
|
|
| 85 |
targets.append(Target(expr=f"{series_columns[0]}", legendFormat=f"{series_columns[0]} (line)"))
|
| 86 |
lines = True
|
| 87 |
bars = True
|
|
|
|
| 145 |
ax2.set_ylabel(first_col)
|
| 146 |
title = f"Bar+Line (Dual Axis) - {second_col} / {first_col}"
|
| 147 |
|
| 148 |
+
# 合併圖例
|
| 149 |
+
h1, l1 = ax.get_legend_handles_labels()
|
| 150 |
+
h2, l2 = ax2.get_legend_handles_labels()
|
| 151 |
+
ax.legend(h1 + h2, l1 + l2, loc="upper left")
|
| 152 |
else:
|
| 153 |
ax.legend(loc="upper left")
|
| 154 |
|
|
|
|
| 177 |
# -----------------------------
|
| 178 |
def pipeline(file, series_choice, dual_axis, rolling_window):
|
| 179 |
"""
|
| 180 |
+
1) 讀 CSV(或示範)
|
| 181 |
+
2) 決定欄位(第一欄做折線 + rolling;第二欄做柱狀或雙軸混合)
|
| 182 |
+
3) 產出 grafanalib Dashboard JSON(反映 dual_axis 與 rolling_window)
|
| 183 |
+
4) 回傳三張圖、dashboard.json 下載路徑、資料表(含 rolling 欄位)、demo.csv 路徑
|
|
|
|
| 184 |
"""
|
| 185 |
df = load_csv(file)
|
| 186 |
|
|
|
|
| 203 |
f.write(dash_json_str)
|
| 204 |
json_path = f.name
|
| 205 |
|
| 206 |
+
# 圖形
|
| 207 |
fig1 = render_line(df, chosen[0])
|
| 208 |
if len(chosen) > 1:
|
| 209 |
fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis))
|
|
|
|
| 215 |
|
| 216 |
fig3, df_with_roll = render_rolling(df.copy(), chosen[0], window=int(rolling_window))
|
| 217 |
|
| 218 |
+
# 產生 demo CSV 檔案(每次呼叫都更新內容)
|
| 219 |
+
demo_df = make_demo_dataframe()
|
| 220 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
|
| 221 |
+
demo_df.to_csv(f, index=False)
|
| 222 |
+
demo_csv_path = f.name
|
| 223 |
+
|
| 224 |
+
return fig1, fig2, fig3, dash_json_str, json_path, df_with_roll, demo_csv_path
|
| 225 |
|
| 226 |
|
| 227 |
# -----------------------------
|
|
|
|
| 229 |
# -----------------------------
|
| 230 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 231 |
gr.Markdown(
|
| 232 |
+
"## grafanalib + Gradio 示範\n"
|
| 233 |
"- 圖1:折線(第一個數值欄)\n"
|
| 234 |
+
"- 圖2:柱狀(第二個數值欄)或雙軸(左柱狀=第二欄、右折線=第一欄)\n"
|
| 235 |
+
"- 圖3:第一個欄位的 Rolling Mean(下拉可調 3/5/10/20)\n\n"
|
| 236 |
+
"✅ 可下載 `dashboard.json` 與 **示範資料 `demo.csv`**;載入頁面即顯示示範資料"
|
| 237 |
)
|
| 238 |
|
| 239 |
with gr.Row():
|
|
|
|
| 254 |
plot2 = gr.Plot(label="圖 2:Bar / Dual Axis")
|
| 255 |
plot3 = gr.Plot(label="圖 3:Rolling Mean")
|
| 256 |
|
| 257 |
+
json_box = gr.Code(label="grafanalib Dashboard JSON", language="json")
|
| 258 |
json_file = gr.File(label="下載 dashboard.json")
|
| 259 |
+
demo_csv_file = gr.File(label="下載示範資料 demo.csv")
|
| 260 |
+
df_view = gr.Dataframe(label="資料預覽(含 rolling 欄位)", wrap=True)
|
| 261 |
|
| 262 |
# 依檔案動態更新欄位選單與表格
|
| 263 |
def probe_columns(file):
|
|
|
|
| 266 |
default_select = numeric_cols[:2]
|
| 267 |
return gr.CheckboxGroup(choices=numeric_cols, value=default_select), df
|
| 268 |
|
| 269 |
+
# 初次載入:以 Demo 資料跑一次(預設就顯示示範資料、三張圖、兩個下載檔)
|
| 270 |
def initial_load():
|
| 271 |
+
f1, f2, f3, dash_json, json_path, df, demo_csv_path = pipeline(
|
| 272 |
file=None, series_choice=[], dual_axis=False, rolling_window="5"
|
| 273 |
)
|
| 274 |
numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
|
| 275 |
default_select = numeric_cols[:2]
|
| 276 |
return (
|
| 277 |
+
f1, f2, f3, # 三張圖
|
| 278 |
+
dash_json, json_path, # JSON 顯示與下載
|
| 279 |
+
df, # DataFrame 顯示(含 rolling 欄位)
|
| 280 |
+
gr.CheckboxGroup(choices=numeric_cols, value=default_select), # 欄位多選
|
| 281 |
+
False, "5", # dual-axis 預設 & rolling window 預設
|
| 282 |
+
demo_csv_path # demo.csv 下載
|
| 283 |
)
|
| 284 |
|
| 285 |
demo.load(
|
| 286 |
initial_load,
|
| 287 |
inputs=None,
|
| 288 |
+
outputs=[plot1, plot2, plot3, json_box, json_file, df_view, series_multiselect, dual_axis_chk, rolling_dd, demo_csv_file],
|
| 289 |
)
|
| 290 |
|
| 291 |
file_in.change(
|
|
|
|
| 297 |
run_btn.click(
|
| 298 |
pipeline,
|
| 299 |
inputs=[file_in, series_multiselect, dual_axis_chk, rolling_dd],
|
| 300 |
+
outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file],
|
| 301 |
)
|
| 302 |
|
| 303 |
if __name__ == "__main__":
|