cwadayi commited on
Commit
b4d5d45
·
verified ·
1 Parent(s): 3f3c7f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +20 -49
app.py CHANGED
@@ -110,14 +110,11 @@ def _style_time_axis(ax):
110
  ax.tick_params(axis="x", labelrotation=20, labelsize=9)
111
  ax.tick_params(axis="y", labelsize=9)
112
  ax.grid(True, which="major", alpha=0.25)
113
- plt.margins(x=0.02, y=0.05) # 留一點白邊
114
 
115
 
116
  def _normalize_times(series: pd.Series) -> pd.Series:
117
- """
118
- 把可能帶時區的時間序列,轉成「UTC 再去時區」的 naive datetime,
119
- 以避免 Matplotlib 在處理 tz-aware 時間造成不一致。
120
- """
121
  s = series.copy()
122
  if getattr(s.dt, "tz", None) is not None:
123
  s = s.dt.tz_convert("UTC").dt.tz_localize(None)
@@ -125,13 +122,13 @@ def _normalize_times(series: pd.Series) -> pd.Series:
125
 
126
 
127
  def _infer_bar_width_days(times: pd.Series) -> float:
128
- """依時間間距估算柱寬(單位:天)。避免整片藍牆。"""
129
  t = pd.Series(times)
130
  if len(t) < 2:
131
  return 60 / 86400 # 60秒
132
  diffs = t.astype("datetime64[ns]").diff().dt.total_seconds().dropna()
133
  med = diffs.median() if not diffs.empty else 60.0
134
- return max(10.0, med * 0.8) / 86400.0 # 取 80% 的中位數,至少 10 秒
135
 
136
 
137
  def render_line(df: pd.DataFrame, col: str):
@@ -147,9 +144,9 @@ def render_line(df: pd.DataFrame, col: str):
147
 
148
 
149
  def render_bar_or_dual(df: pd.DataFrame, second_col: str, first_col: str, dual_axis: bool):
150
- # --- 修正點:正確轉換 Series 為原生 datetime(處理時區 + .dt.to_pydatetime()) ---
151
  times = _normalize_times(df["time"])
152
- x = mdates.date2num(times.dt.to_pydatetime())
 
153
 
154
  fig, ax = plt.subplots(figsize=(6.5, 3.6))
155
  width = _infer_bar_width_days(times)
@@ -202,34 +199,25 @@ def _placeholder_fig():
202
  # Main pipeline
203
  # -----------------------------
204
  def pipeline(file, series_choice, dual_axis, rolling_window):
205
- """
206
- 1) 讀 CSV(或示範)
207
- 2) 第一欄→折線與 rolling;第二欄→柱狀/雙軸
208
- 3) 產出 grafanalib Dashboard JSON
209
- 4) 回傳三張圖、dashboard.json 路徑、資料表(含 rolling)、demo.csv 路徑
210
- """
211
  df = load_csv(file)
212
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
213
  if not numeric_cols:
214
- raise ValueError("未找到可繪圖的數值欄位。請提供至少一個數值欄位(除了 time 以外)。")
215
 
216
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
217
  if not chosen:
218
  chosen = numeric_cols[:2]
219
 
220
- # Dashboard JSON
221
  dash_json = build_grafanalib_dashboard(chosen, bool(dual_axis), int(rolling_window))
222
  dash_json_str = json.dumps(dash_json, ensure_ascii=False, indent=2, default=str)
223
  with tempfile.NamedTemporaryFile(delete=False, suffix=".json", mode="w", encoding="utf-8") as f:
224
  f.write(dash_json_str)
225
  json_path = f.name
226
 
227
- # Figures
228
  fig1 = render_line(df, chosen[0])
229
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else _placeholder_fig()
230
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], window=int(rolling_window))
231
 
232
- # demo.csv(每次都用全新隨機)
233
  demo_df = make_demo_dataframe()
234
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
235
  demo_df.to_csv(f, index=False)
@@ -242,7 +230,6 @@ def pipeline(file, series_choice, dual_axis, rolling_window):
242
  # Regenerate demo helper
243
  # -----------------------------
244
  def regenerate_demo(series_choice, dual_axis, rolling_window):
245
- """忽略上傳檔案,使用全新隨機示範資料。"""
246
  return pipeline(file=None, series_choice=series_choice, dual_axis=dual_axis, rolling_window=rolling_window)
247
 
248
 
@@ -250,24 +237,18 @@ def regenerate_demo(series_choice, dual_axis, rolling_window):
250
  # UI
251
  # -----------------------------
252
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
253
- gr.Markdown(
254
- "## Grafana-like 隨機示範\n"
255
- "為手機優化:精簡時間軸、控制刻度、加上網格與間距。"
256
- )
257
 
258
  with gr.Row():
259
  file_in = gr.File(label="上傳 CSV(可空,會用隨機示範資料)", file_types=[".csv"])
260
- series_multiselect = gr.CheckboxGroup(
261
- label="數值欄位(第一欄→折線/rolling;第二欄→柱狀/雙軸)",
262
- choices=[]
263
- )
264
 
265
  with gr.Row():
266
  dual_axis_chk = gr.Checkbox(label="第二面板啟用雙軸", value=False)
267
  rolling_dd = gr.Dropdown(label="Rolling window", choices=["3", "5", "10", "20"], value="5")
268
 
269
  with gr.Row():
270
- run_btn = gr.Button("用『上傳/當前資料』產生 Dashboard")
271
  regen_btn = gr.Button("🔁 重新產生示範資料(隨機)")
272
 
273
  with gr.Row():
@@ -282,7 +263,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
282
  demo_csv_file = gr.File(label="下載示範資料 demo.csv")
283
  df_view = gr.Dataframe(label="資料預覽(含 rolling 欄位)", wrap=True)
284
 
285
- # 檔案上傳時,更新可選欄位與預覽
286
  def probe_columns(file):
287
  df = load_csv(file)
288
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
@@ -290,7 +270,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
290
 
291
  file_in.change(probe_columns, inputs=[file_in], outputs=[series_multiselect, df_view])
292
 
293
- # 初次載入:以隨機示範資料跑一次並更新欄位清單
294
  def initial_load():
295
  f1, f2, f3, dash_json, json_path, df, demo_csv_path = pipeline(
296
  file=None, series_choice=[], dual_axis=False, rolling_window="5"
@@ -302,26 +281,18 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
302
  False, "5"
303
  )
304
 
305
- demo.load(
306
- initial_load,
307
- inputs=None,
308
- outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file,
309
- series_multiselect, dual_axis_chk, rolling_dd],
310
- )
311
 
312
- # 用「上傳/當前資料」產生
313
- run_btn.click(
314
- pipeline,
315
- inputs=[file_in, series_multiselect, dual_axis_chk, rolling_dd],
316
- outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file],
317
- )
318
 
319
- # 🔁 重新產生「隨機示範資料」
320
- regen_btn.click(
321
- regenerate_demo,
322
- inputs=[series_multiselect, dual_axis_chk, rolling_dd],
323
- outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file],
324
- )
325
 
326
  if __name__ == "__main__":
327
  demo.launch()
 
110
  ax.tick_params(axis="x", labelrotation=20, labelsize=9)
111
  ax.tick_params(axis="y", labelsize=9)
112
  ax.grid(True, which="major", alpha=0.25)
113
+ plt.margins(x=0.02, y=0.05)
114
 
115
 
116
  def _normalize_times(series: pd.Series) -> pd.Series:
117
+ """把可能帶時區的時間序列轉成 naive datetime"""
 
 
 
118
  s = series.copy()
119
  if getattr(s.dt, "tz", None) is not None:
120
  s = s.dt.tz_convert("UTC").dt.tz_localize(None)
 
122
 
123
 
124
  def _infer_bar_width_days(times: pd.Series) -> float:
125
+ """依時間間距估算柱寬(單位:天)"""
126
  t = pd.Series(times)
127
  if len(t) < 2:
128
  return 60 / 86400 # 60秒
129
  diffs = t.astype("datetime64[ns]").diff().dt.total_seconds().dropna()
130
  med = diffs.median() if not diffs.empty else 60.0
131
+ return max(10.0, med * 0.8) / 86400.0
132
 
133
 
134
  def render_line(df: pd.DataFrame, col: str):
 
144
 
145
 
146
  def render_bar_or_dual(df: pd.DataFrame, second_col: str, first_col: str, dual_axis: bool):
 
147
  times = _normalize_times(df["time"])
148
+ # 修正:to_pydatetime → to_list() 避免 FutureWarning
149
+ x = mdates.date2num(times.dt.to_pydatetime().tolist())
150
 
151
  fig, ax = plt.subplots(figsize=(6.5, 3.6))
152
  width = _infer_bar_width_days(times)
 
199
  # Main pipeline
200
  # -----------------------------
201
  def pipeline(file, series_choice, dual_axis, rolling_window):
 
 
 
 
 
 
202
  df = load_csv(file)
203
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
204
  if not numeric_cols:
205
+ raise ValueError("未找到可繪圖的數值欄位。")
206
 
207
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
208
  if not chosen:
209
  chosen = numeric_cols[:2]
210
 
 
211
  dash_json = build_grafanalib_dashboard(chosen, bool(dual_axis), int(rolling_window))
212
  dash_json_str = json.dumps(dash_json, ensure_ascii=False, indent=2, default=str)
213
  with tempfile.NamedTemporaryFile(delete=False, suffix=".json", mode="w", encoding="utf-8") as f:
214
  f.write(dash_json_str)
215
  json_path = f.name
216
 
 
217
  fig1 = render_line(df, chosen[0])
218
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else _placeholder_fig()
219
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], window=int(rolling_window))
220
 
 
221
  demo_df = make_demo_dataframe()
222
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
223
  demo_df.to_csv(f, index=False)
 
230
  # Regenerate demo helper
231
  # -----------------------------
232
  def regenerate_demo(series_choice, dual_axis, rolling_window):
 
233
  return pipeline(file=None, series_choice=series_choice, dual_axis=dual_axis, rolling_window=rolling_window)
234
 
235
 
 
237
  # UI
238
  # -----------------------------
239
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
240
+ gr.Markdown("## Grafana-like 隨機示範\n手機優化:精簡時間軸、網格、隨機資料")
 
 
 
241
 
242
  with gr.Row():
243
  file_in = gr.File(label="上傳 CSV(可空,會用隨機示範資料)", file_types=[".csv"])
244
+ series_multiselect = gr.CheckboxGroup(label="數值欄位", choices=[])
 
 
 
245
 
246
  with gr.Row():
247
  dual_axis_chk = gr.Checkbox(label="第二面板啟用雙軸", value=False)
248
  rolling_dd = gr.Dropdown(label="Rolling window", choices=["3", "5", "10", "20"], value="5")
249
 
250
  with gr.Row():
251
+ run_btn = gr.Button("產生 Dashboard")
252
  regen_btn = gr.Button("🔁 重新產生示範資料(隨機)")
253
 
254
  with gr.Row():
 
263
  demo_csv_file = gr.File(label="下載示範資料 demo.csv")
264
  df_view = gr.Dataframe(label="資料預覽(含 rolling 欄位)", wrap=True)
265
 
 
266
  def probe_columns(file):
267
  df = load_csv(file)
268
  numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
 
270
 
271
  file_in.change(probe_columns, inputs=[file_in], outputs=[series_multiselect, df_view])
272
 
 
273
  def initial_load():
274
  f1, f2, f3, dash_json, json_path, df, demo_csv_path = pipeline(
275
  file=None, series_choice=[], dual_axis=False, rolling_window="5"
 
281
  False, "5"
282
  )
283
 
284
+ demo.load(initial_load,
285
+ inputs=None,
286
+ outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file,
287
+ series_multiselect, dual_axis_chk, rolling_dd])
 
 
288
 
289
+ run_btn.click(pipeline,
290
+ inputs=[file_in, series_multiselect, dual_axis_chk, rolling_dd],
291
+ outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file])
 
 
 
292
 
293
+ regen_btn.click(regenerate_demo,
294
+ inputs=[series_multiselect, dual_axis_chk, rolling_dd],
295
+ outputs=[plot1, plot2, plot3, json_box, json_file, df_view, demo_csv_file])
 
 
 
296
 
297
  if __name__ == "__main__":
298
  demo.launch()