cwadayi commited on
Commit
b8299c5
·
verified ·
1 Parent(s): a3a987c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -104
app.py CHANGED
@@ -7,8 +7,7 @@ import gradio as gr
7
  import pandas as pd
8
  import matplotlib.pyplot as plt
9
 
10
- # === grafanalib: 只用來「定義」Grafana Dashboard JSON 結構 ===
11
- # 注意:實際畫圖在 Space 端用 Matplotlib 完成
12
  from grafanalib.core import (
13
  Dashboard, Graph, Row, Target, YAxis, YAxes, Time
14
  )
@@ -20,7 +19,7 @@ TAIPEI = tz.gettz("Asia/Taipei")
20
  # Demo / Data loading
21
  # -----------------------------
22
  def make_demo_dataframe() -> pd.DataFrame:
23
- """產生示範資料(1 小時、每分鐘 1 點)"""
24
  t0 = datetime.now(tz=TAIPEI) - timedelta(minutes=60)
25
  times = [t0 + timedelta(minutes=i) for i in range(61)]
26
  amp = [0.5 + 0.4 * __import__("math").sin(i / 6.0) for i in range(61)]
@@ -30,7 +29,7 @@ def make_demo_dataframe() -> pd.DataFrame:
30
 
31
 
32
  def load_csv(file: gr.File | None) -> pd.DataFrame:
33
- """支援上傳 CSV(需含時間欄:time/timestamp/datetime/date),自動轉 tz=Asia/Taipei"""
34
  if file is None:
35
  return make_demo_dataframe()
36
 
@@ -43,13 +42,12 @@ def load_csv(file: gr.File | None) -> pd.DataFrame:
43
  df[time_col] = pd.to_datetime(df[time_col])
44
  df.rename(columns={time_col: "time"}, inplace=True)
45
 
46
- # 轉換 / 補上時區
47
  if getattr(df["time"].dt, "tz", None) is None:
48
  df["time"] = df["time"].dt.tz_localize(TAIPEI)
49
  else:
50
  df["time"] = df["time"].dt.tz_convert(TAIPEI)
51
 
52
- # 依時間排序,避免畫圖時軸亂序
53
  df = df.sort_values("time").reset_index(drop=True)
54
  return df
55
 
@@ -61,59 +59,42 @@ def build_grafanalib_dashboard(series_columns: list[str]) -> dict:
61
  """
62
  產生 Grafana Dashboard JSON:
63
  - Panel 1:Line(第一個數值欄位)
64
- - Panel 2:Bar(第二個數值欄位;若不存在則忽略)
65
- - Panel 3:Line(第一個數值欄位的 5 點 rolling mean
66
  """
67
  panels = []
68
 
69
- # Panel 1: 第一個欄位(折線)
70
  panels.append(
71
  Graph(
72
  title=f"Time Series - {series_columns[0]}",
73
  dataSource="(example)",
74
  targets=[Target(expr=f"{series_columns[0]}", legendFormat=series_columns[0])],
75
- # 折線圖
76
- lines=True,
77
- bars=False,
78
- points=False,
79
- yAxes=YAxes(
80
- left=YAxis(format="short"),
81
- right=YAxis(format="short"),
82
- ),
83
  )
84
  )
85
 
86
- # Panel 2: 第二個欄位(柱狀)- 若有第二個欄位
87
  if len(series_columns) > 1:
88
  panels.append(
89
  Graph(
90
  title=f"Event Count (Bar) - {series_columns[1]}",
91
  dataSource="(example)",
92
  targets=[Target(expr=f"{series_columns[1]}", legendFormat=series_columns[1])],
93
- # 柱狀圖
94
- lines=False,
95
- bars=True,
96
- points=False,
97
- yAxes=YAxes(
98
- left=YAxis(format="short"),
99
- right=YAxis(format="short"),
100
- ),
101
  )
102
  )
103
 
104
- # Panel 3: 第一個欄位 Rolling Mean(折線)
105
  panels.append(
106
  Graph(
107
  title=f"Rolling Mean (5) - {series_columns[0]}",
108
  dataSource="(example)",
109
  targets=[Target(expr=f"{series_columns[0]}_rolling5", legendFormat=f"{series_columns[0]}_rolling5")],
110
- lines=True,
111
- bars=False,
112
- points=False,
113
- yAxes=YAxes(
114
- left=YAxis(format="short"),
115
- right=YAxis(format="short"),
116
- ),
117
  )
118
  )
119
 
@@ -127,55 +108,42 @@ def build_grafanalib_dashboard(series_columns: list[str]) -> dict:
127
 
128
 
129
  # -----------------------------
130
- # Matplotlib rendering
131
  # -----------------------------
132
- def render_matplotlib(df: pd.DataFrame, value_cols: list[str]):
133
- """
134
- 依據 df 實際畫圖:
135
- - 圖1:第一個欄位(折線)
136
- - 圖2:第二個欄位(柱狀;若不存在則略過)
137
- - 圖3:第一個欄位的 5 點 rolling mean(折線)
138
- 回傳 List[Figure],給 gr.Gallery 顯示。
139
- """
140
- figs = []
141
-
142
- # 圖1:折線(第一欄)
143
- fig1 = plt.figure()
144
- plt.plot(df["time"], df[value_cols[0]])
145
- plt.title(f"Time Series - {value_cols[0]}")
146
  plt.xlabel("Time")
147
- plt.ylabel(value_cols[0])
148
  plt.xticks(rotation=20)
149
  plt.tight_layout()
150
- figs.append(fig1)
151
-
152
- # 圖2:柱狀(第二欄,若存在)
153
- if len(value_cols) > 1:
154
- fig2 = plt.figure()
155
- # 使用條狀圖
156
- plt.bar(df["time"], df[value_cols[1]])
157
- plt.title(f"Event Count (Bar) - {value_cols[1]}")
158
- plt.xlabel("Time")
159
- plt.ylabel(value_cols[1])
160
- plt.xticks(rotation=20)
161
- plt.tight_layout()
162
- figs.append(fig2)
163
 
164
- # 圖3:Rolling Mean(對第一欄做 5 點移動平均)
165
- rolling_col = f"{value_cols[0]}_rolling5"
166
- if rolling_col not in df.columns:
167
- df[rolling_col] = df[value_cols[0]].rolling(window=5, min_periods=1, center=False).mean()
168
 
169
- fig3 = plt.figure()
170
- plt.plot(df["time"], df[rolling_col])
171
- plt.title(f"Rolling Mean (5) - {value_cols[0]}")
 
172
  plt.xlabel("Time")
173
- plt.ylabel(rolling_col)
174
  plt.xticks(rotation=20)
175
  plt.tight_layout()
176
- figs.append(fig3)
 
177
 
178
- return figs, df # 回傳 df 讓上游可帶出 rolling 欄位
 
 
 
 
 
 
 
 
 
 
 
179
 
180
 
181
  # -----------------------------
@@ -183,90 +151,93 @@ def render_matplotlib(df: pd.DataFrame, value_cols: list[str]):
183
  # -----------------------------
184
  def pipeline(file, series_choice):
185
  """
186
- 主要流程:
187
- 1) 讀 CSV(或示範資料)
188
- 2) 選擇要展示的欄位(第一欄必定存在;第二欄選配)
189
- 3) 建立 grafanalib Dashboard JSON(含第三個 rolling 面板)
190
- 4) Matplotlib 繪圖(第2圖為柱狀;第3圖為 rolling mean
191
  """
192
  df = load_csv(file)
193
 
194
- # 取得數值欄位(排除 time)
195
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
196
  if not numeric_cols:
197
  raise ValueError("未找到可繪圖的數值欄位。請提供至少一個數值欄位(除了 time 以外)。")
198
 
199
- # 使用者選擇優先;否則預設取前兩個
200
  chosen = series_choice or numeric_cols[:2]
201
  chosen = [c for c in chosen if c in numeric_cols]
202
  if not chosen:
203
  chosen = numeric_cols[:2]
204
 
205
- # JSON(第三面板會參照 f"{chosen[0]}_rolling5")
206
  dash_json = build_grafanalib_dashboard(chosen)
207
-
208
- # 畫圖(第2圖為柱狀;第3圖為 rolling)
209
- figs, df_with_rolling = render_matplotlib(df.copy(), chosen)
210
-
211
- # JSON 輸出與下載檔
212
  dash_json_str = json.dumps(dash_json, ensure_ascii=False, indent=2, default=str)
213
  json_bytes = io.BytesIO(dash_json_str.encode("utf-8"))
214
  json_bytes.name = "dashboard.json"
215
 
216
- return figs, dash_json_str, json_bytes, df_with_rolling
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
 
219
  # -----------------------------
220
- # UI
221
  # -----------------------------
222
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
223
  gr.Markdown(
224
  "# grafanalib 生成 Dashboard + Gradio 呈現\n"
225
- "- 第一個面板:折線(第一個數值欄)\n"
226
- "- 第二個面板:柱狀(第二個數值欄,若有)\n"
227
- "- 第三個面板:第一個欄位的 5 點移動平均(折線)\n\n"
228
  "**說明**:grafanalib 產生可匯入 Grafana 的 Dashboard JSON;下方 Matplotlib 圖為 Space 端即時呈現。"
229
  )
230
 
231
  with gr.Row():
232
  file_in = gr.File(label="上傳 CSV(可空,會用示範資料)", file_types=[".csv"])
233
  series_multiselect = gr.CheckboxGroup(
234
- label="選擇要呈現的數值欄位(第一個欄位用於折線&rolling;第二個欄位用於柱狀,選配)",
235
  choices=[]
236
  )
237
 
238
  run_btn = gr.Button("產生 Dashboard 並繪圖")
239
 
240
  with gr.Row():
241
- gallery = gr.Gallery(label="圖表預覽(1:Line, 2:Bar, 3:Rolling Line)", height=420)
242
-
243
- with gr.Row():
244
- json_box = gr.Code(label="grafanalib Dashboard JSON(可匯入真正的 Grafana)", language="json")
245
-
246
- with gr.Row():
247
- json_file = gr.File(label="下載 dashboard.json")
248
 
 
 
249
  df_view = gr.Dataframe(label="資料預覽(包含 rolling 欄位)", wrap=True)
250
 
251
  # 依檔案動態更新欄位選單與表格
252
  def probe_columns(file):
253
  df = load_csv(file)
254
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
255
- # 預設勾選前兩個(若只有一個,就只勾一個)
256
  default_select = numeric_cols[:2]
257
  return gr.CheckboxGroup(choices=numeric_cols, value=default_select), df
258
 
259
  # 初次載入:以 Demo 資料跑一次
260
  def initial_load():
261
- figs, dash_json, json_file_obj, df = pipeline(file=None, series_choice=[])
262
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
263
  default_select = numeric_cols[:2]
264
- return figs, dash_json, json_file_obj, df, gr.CheckboxGroup(choices=numeric_cols, value=default_select)
265
 
266
  demo.load(
267
  initial_load,
268
  inputs=None,
269
- outputs=[gallery, json_box, json_file, df_view, series_multiselect]
270
  )
271
 
272
  file_in.change(
@@ -278,7 +249,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
278
  run_btn.click(
279
  pipeline,
280
  inputs=[file_in, series_multiselect],
281
- outputs=[gallery, json_box, json_file, df_view],
282
  )
283
 
284
  if __name__ == "__main__":
 
7
  import pandas as pd
8
  import matplotlib.pyplot as plt
9
 
10
+ # grafanalib:只用來「定義」Grafana Dashboard JSON(不在這裡畫圖)
 
11
  from grafanalib.core import (
12
  Dashboard, Graph, Row, Target, YAxis, YAxes, Time
13
  )
 
19
  # Demo / Data loading
20
  # -----------------------------
21
  def make_demo_dataframe() -> pd.DataFrame:
22
+ """產生示範資料(1 小時、每分鐘一點)"""
23
  t0 = datetime.now(tz=TAIPEI) - timedelta(minutes=60)
24
  times = [t0 + timedelta(minutes=i) for i in range(61)]
25
  amp = [0.5 + 0.4 * __import__("math").sin(i / 6.0) for i in range(61)]
 
29
 
30
 
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
 
 
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
53
 
 
59
  """
60
  產生 Grafana Dashboard JSON:
61
  - Panel 1:Line(第一個數值欄位)
62
+ - Panel 2:Bar(第二個數值欄位;若不存在則不放)
63
+ - Panel 3:Line(第一個數值欄位的 5 點 Rolling Mean
64
  """
65
  panels = []
66
 
67
+ # Panel 1:折線
68
  panels.append(
69
  Graph(
70
  title=f"Time Series - {series_columns[0]}",
71
  dataSource="(example)",
72
  targets=[Target(expr=f"{series_columns[0]}", legendFormat=series_columns[0])],
73
+ lines=True, bars=False, points=False,
74
+ yAxes=YAxes(left=YAxis(format="short"), right=YAxis(format="short")),
 
 
 
 
 
 
75
  )
76
  )
77
 
78
+ # Panel 2:柱狀(若有第二欄)
79
  if len(series_columns) > 1:
80
  panels.append(
81
  Graph(
82
  title=f"Event Count (Bar) - {series_columns[1]}",
83
  dataSource="(example)",
84
  targets=[Target(expr=f"{series_columns[1]}", legendFormat=series_columns[1])],
85
+ lines=False, bars=True, points=False,
86
+ yAxes=YAxes(left=YAxis(format="short"), right=YAxis(format="short")),
 
 
 
 
 
 
87
  )
88
  )
89
 
90
+ # Panel 3Rolling Mean(5 點)
91
  panels.append(
92
  Graph(
93
  title=f"Rolling Mean (5) - {series_columns[0]}",
94
  dataSource="(example)",
95
  targets=[Target(expr=f"{series_columns[0]}_rolling5", legendFormat=f"{series_columns[0]}_rolling5")],
96
+ lines=True, bars=False, points=False,
97
+ yAxes=YAxes(left=YAxis(format="short"), right=YAxis(format="short")),
 
 
 
 
 
98
  )
99
  )
100
 
 
108
 
109
 
110
  # -----------------------------
111
+ # Matplotlib rendering(各回傳單一 Figure)
112
  # -----------------------------
113
+ def render_line(df: pd.DataFrame, col: str):
114
+ fig = plt.figure()
115
+ plt.plot(df["time"], df[col])
116
+ plt.title(f"Time Series - {col}")
 
 
 
 
 
 
 
 
 
 
117
  plt.xlabel("Time")
118
+ plt.ylabel(col)
119
  plt.xticks(rotation=20)
120
  plt.tight_layout()
121
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
122
 
 
 
 
 
123
 
124
+ def render_bar(df: pd.DataFrame, col: str):
125
+ fig = plt.figure()
126
+ plt.bar(df["time"], df[col])
127
+ plt.title(f"Event Count (Bar) - {col}")
128
  plt.xlabel("Time")
129
+ plt.ylabel(col)
130
  plt.xticks(rotation=20)
131
  plt.tight_layout()
132
+ return fig
133
+
134
 
135
+ def render_rolling(df: pd.DataFrame, col: str, window: int = 5):
136
+ roll_col = f"{col}_rolling{window}"
137
+ if roll_col not in df.columns:
138
+ df[roll_col] = df[col].rolling(window=window, min_periods=1).mean()
139
+ fig = plt.figure()
140
+ plt.plot(df["time"], df[roll_col])
141
+ plt.title(f"Rolling Mean ({window}) - {col}")
142
+ plt.xlabel("Time")
143
+ plt.ylabel(roll_col)
144
+ plt.xticks(rotation=20)
145
+ plt.tight_layout()
146
+ return fig, df
147
 
148
 
149
  # -----------------------------
 
151
  # -----------------------------
152
  def pipeline(file, series_choice):
153
  """
154
+ 流程:
155
+ 1) 讀 CSV(或示範)
156
+ 2) 決定欄位(第一欄做折線 + rolling;第二欄做柱狀(可選))
157
+ 3) 產出 grafanalib Dashboard JSON
158
+ 4) 回傳三張圖(gr.Plot 能吃 Matplotlib Figure
159
  """
160
  df = load_csv(file)
161
 
162
+ # 數值欄位
163
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
164
  if not numeric_cols:
165
  raise ValueError("未找到可繪圖的數值欄位。請提供至少一個數值欄位(除了 time 以外)。")
166
 
 
167
  chosen = series_choice or numeric_cols[:2]
168
  chosen = [c for c in chosen if c in numeric_cols]
169
  if not chosen:
170
  chosen = numeric_cols[:2]
171
 
172
+ # Dashboard JSON
173
  dash_json = build_grafanalib_dashboard(chosen)
 
 
 
 
 
174
  dash_json_str = json.dumps(dash_json, ensure_ascii=False, indent=2, default=str)
175
  json_bytes = io.BytesIO(dash_json_str.encode("utf-8"))
176
  json_bytes.name = "dashboard.json"
177
 
178
+ # Figures
179
+ fig1 = render_line(df, chosen[0])
180
+ fig2 = render_bar(df, chosen[1]) if len(chosen) > 1 else None
181
+ fig3, df_with_roll = render_rolling(df.copy(), chosen[0], window=5)
182
+
183
+ # 若沒有第二欄,給一張簡單 placeholder(避免輸出 None 造成型別不符)
184
+ if fig2 is None:
185
+ fig2 = plt.figure()
186
+ plt.text(0.5, 0.5, "第二張圖:未選第二數值欄", ha="center", va="center", fontsize=12)
187
+ plt.axis("off")
188
+ plt.tight_layout()
189
+
190
+ return fig1, fig2, fig3, dash_json_str, json_bytes, df_with_roll
191
 
192
 
193
  # -----------------------------
194
+ # UI(使用三個 gr.Plot 取代 Gallery)
195
  # -----------------------------
196
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
197
  gr.Markdown(
198
  "# grafanalib 生成 Dashboard + Gradio 呈現\n"
199
+ "- 圖1:折線(第一個數值欄)\n"
200
+ "- 圖2:柱狀(第二個數值欄;若未選則顯示提示)\n"
201
+ "- 圖3:第一個欄位的 5 點移動平均(折線)\n\n"
202
  "**說明**:grafanalib 產生可匯入 Grafana 的 Dashboard JSON;下方 Matplotlib 圖為 Space 端即時呈現。"
203
  )
204
 
205
  with gr.Row():
206
  file_in = gr.File(label="上傳 CSV(可空,會用示範資料)", file_types=[".csv"])
207
  series_multiselect = gr.CheckboxGroup(
208
+ label="選擇要呈現的數值欄位(第一欄→折線與 Rolling;第二欄→柱狀,選配)",
209
  choices=[]
210
  )
211
 
212
  run_btn = gr.Button("產生 Dashboard 並繪圖")
213
 
214
  with gr.Row():
215
+ plot1 = gr.Plot(label="1Line")
216
+ plot2 = gr.Plot(label="圖 2:Bar")
217
+ plot3 = gr.Plot(label="圖 3:Rolling Mean")
 
 
 
 
218
 
219
+ json_box = gr.Code(label="grafanalib Dashboard JSON(可匯入真正的 Grafana)", language="json")
220
+ json_file = gr.File(label="下載 dashboard.json")
221
  df_view = gr.Dataframe(label="資料預覽(包含 rolling 欄位)", wrap=True)
222
 
223
  # 依檔案動態更新欄位選單與表格
224
  def probe_columns(file):
225
  df = load_csv(file)
226
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
 
227
  default_select = numeric_cols[:2]
228
  return gr.CheckboxGroup(choices=numeric_cols, value=default_select), df
229
 
230
  # 初次載入:以 Demo 資料跑一次
231
  def initial_load():
232
+ f1, f2, f3, dash_json, json_file_obj, df = pipeline(file=None, series_choice=[])
233
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
234
  default_select = numeric_cols[:2]
235
+ return f1, f2, f3, dash_json, json_file_obj, df, gr.CheckboxGroup(choices=numeric_cols, value=default_select)
236
 
237
  demo.load(
238
  initial_load,
239
  inputs=None,
240
+ outputs=[plot1, plot2, plot3, json_box, json_file, df_view, series_multiselect],
241
  )
242
 
243
  file_in.change(
 
249
  run_btn.click(
250
  pipeline,
251
  inputs=[file_in, series_multiselect],
252
+ outputs=[plot1, plot2, plot3, json_box, json_file, df_view],
253
  )
254
 
255
  if __name__ == "__main__":