cwadayi commited on
Commit
e499029
·
verified ·
1 Parent(s): 4f0d8cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -40
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
- return make_demo_dataframe()
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["time"] = df["time"].dt.tz_convert(TAIPEI)
 
 
 
 
 
 
 
 
 
 
 
 
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
- lines_labels = [(l, l.get_label()) for l in ax.get_legend_handles_labels()[0]]
152
- lines_labels += [(l, l.get_label()) for l in ax2.get_legend_handles_labels()[0]]
153
- ax.legend([h for h, _ in lines_labels], [lab for _, lab in lines_labels], loc="upper left")
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
- 1) CSV(或示範)
184
- 2) 決定欄位(第一欄做折線 + rolling;第二欄做柱狀或雙軸混合)
185
- 3) 產出 grafanalib Dashboard JSON(反映 dual_axis 與 rolling_window)
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
- # Figures
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
- return fig1, fig2, fig3, dash_json_str, json_path, df_with_roll
 
 
 
 
 
 
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
- "# grafanalib 生成 Dashboard + Gradio 呈現\n"
230
  "- 圖1:折線(第一個數值欄)\n"
231
- "- 圖2:柱狀(第二個數值欄)或「雙軸:左柱狀(第二欄) + 右折線(第一欄)」\n"
232
- "- 圖3:第一個欄位的 Rolling Mean(下拉可調視窗:3/5/10/20)\n\n"
233
- "**說明**:grafanalib 產生可匯入 Grafana Dashboard JSON;下方 Matplotlib 圖為 Space 端即時呈現。"
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(可匯入真正的 Grafana)", language="json")
255
  json_file = gr.File(label="下載 dashboard.json")
256
- df_view = gr.Dataframe(label="資料預覽(包含 rolling 欄位)", wrap=True)
 
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, dash_json, json_path, df,
274
- gr.CheckboxGroup(choices=numeric_cols, value=default_select),
275
- False, "5"
 
 
 
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__":