cwadayi commited on
Commit
ab4b958
·
verified ·
1 Parent(s): c8edb35

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -31
app.py CHANGED
@@ -4,6 +4,7 @@ import re
4
  import tempfile
5
  from datetime import datetime, timedelta
6
  from dateutil import tz
 
7
 
8
  import gradio as gr
9
  import pandas as pd
@@ -59,12 +60,15 @@ def normalize_drive_url(url: str) -> str:
59
 
60
 
61
  # -----------------------------
62
- # Demo / Data loading
63
  # -----------------------------
64
- def make_demo_dataframe() -> pd.DataFrame:
65
- """隨機示範資料:含經緯度 + pid"""
66
- t0 = datetime.now(tz=TAIPEI) - timedelta(minutes=60)
67
- times = [t0 + timedelta(minutes=i) for i in range(61)]
 
 
 
68
  amp = np.random.rand(len(times))
69
  cnt = np.random.randint(0, 11, size=len(times))
70
  lats = np.random.uniform(21.8, 25.3, size=len(times))
@@ -77,7 +81,7 @@ def make_demo_dataframe() -> pd.DataFrame:
77
  "lon": lons
78
  })
79
  df["pid"] = np.arange(len(df))
80
- return df
81
 
82
 
83
  def _finalize_time(df: pd.DataFrame) -> pd.DataFrame:
@@ -128,18 +132,20 @@ def load_drive_csv(sheet_or_file_url: str) -> pd.DataFrame:
128
  raise ValueError(f"Google 連結載入失敗:{str(e)}")
129
 
130
 
131
- def load_data(source: str, file: gr.File | None = None, sheet_url: str = "") -> pd.DataFrame:
132
- """依來源載入資料:demo / upload / drive"""
133
  if source == "drive":
134
  if not sheet_url:
135
  raise ValueError("請選擇 Google 連結")
136
- return load_drive_csv(sheet_url)
 
137
  elif source == "upload":
138
  if file is None:
139
  raise ValueError("請上傳 CSV 檔")
140
- return load_csv(file)
 
141
  else:
142
- return make_demo_dataframe()
143
 
144
 
145
  # -----------------------------
@@ -434,11 +440,11 @@ def pick_detail(df: pd.DataFrame, choice: str) -> pd.DataFrame:
434
 
435
 
436
  # -----------------------------
437
- # Main pipeline
438
  # -----------------------------
439
- def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, start_time, end_time, show_heatmap):
440
  try:
441
- df = load_data(source, file, sheet_url)
442
  df = filter_data(df, start_time, end_time) # 新增過濾
443
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon", "pid"] and pd.api.types.is_numeric_dtype(df[c])]
444
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
@@ -465,7 +471,7 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
465
  default_choice = point_choices[0] if point_choices else ""
466
  detail_df = pick_detail(df, default_choice)
467
 
468
- demo_df = make_demo_dataframe()
469
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
470
  demo_df.to_csv(f, index=False)
471
  demo_csv_path = f.name
@@ -476,7 +482,8 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
476
  demo_csv_path,
477
  gr.Dropdown(choices=point_choices, value=default_choice),
478
  detail_df,
479
- "" # 錯誤訊息清空
 
480
  )
481
  except Exception as e:
482
  return (
@@ -485,12 +492,13 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
485
  None,
486
  gr.Dropdown(choices=[], value=None),
487
  pd.DataFrame(),
488
- str(e)
 
489
  )
490
 
491
 
492
- def regenerate_demo(series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, current_choice, start_time, end_time, show_heatmap):
493
- return pipeline("demo", None, "", series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, start_time, end_time, show_heatmap)
494
 
495
 
496
  def update_detail(df: pd.DataFrame, choice: str):
@@ -498,10 +506,10 @@ def update_detail(df: pd.DataFrame, choice: str):
498
 
499
 
500
  # -----------------------------
501
- # UI 優化:使用 Tab 分頁、添加錯誤顯示、時間過濾、熱圖選項、優化圖表佈局
502
  # -----------------------------
503
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
504
- gr.Markdown("## 優化版 Grafana-like Demo + Folium Map(支援 Google Drive / Sheets,新增熱圖與 Gauge)")
505
 
506
  with gr.Row():
507
  with gr.Column(scale=1):
@@ -531,7 +539,8 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
531
 
532
  with gr.Row():
533
  run_btn = gr.Button("產生 Dashboard", scale=1)
534
- regen_btn = gr.Button("🔁 重新產生示範資料", scale=1)
 
535
 
536
  error_msg = gr.Markdown(value="", label="錯誤訊息", visible=True)
537
 
@@ -568,7 +577,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
568
  def probe_columns(source, file, preset_url, start_time, end_time):
569
  sheet_url = preset_url if source == "drive" else ""
570
  try:
571
- df = load_data(source, file, sheet_url)
572
  df = filter_data(df, start_time, end_time)
573
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon", "pid"] and pd.api.types.is_numeric_dtype(df[c])]
574
  return gr.CheckboxGroup(choices=numeric_cols, value=numeric_cols[:2]), df, ""
@@ -590,35 +599,61 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
590
  json_box, json_file, df_view,
591
  demo_csv_file,
592
  point_selector, detail_view,
593
- error_msg
 
594
  ]
595
  )
596
 
597
- # 產生 / 重新產生
598
  run_btn.click(
599
  pipeline,
600
- inputs=[source_radio, file_in, preset_dd, series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, start_time_in, end_time_in, heatmap_chk],
601
  outputs=[
602
  plot1, plot2, plot3, plot4, map_out,
603
  json_box, json_file, df_view,
604
  demo_csv_file,
605
  point_selector, detail_view,
606
- error_msg
 
607
  ]
608
  )
609
 
610
- regen_btn.click(
611
- regenerate_demo,
612
- inputs=[series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, point_selector, start_time_in, end_time_in, heatmap_chk],
613
  outputs=[
614
  plot1, plot2, plot3, plot4, map_out,
615
  json_box, json_file, df_view,
616
  demo_csv_file,
617
  point_selector, detail_view,
618
- error_msg
 
619
  ]
620
  )
621
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
  point_selector.change(
623
  update_detail,
624
  inputs=[df_view, point_selector],
 
4
  import tempfile
5
  from datetime import datetime, timedelta
6
  from dateutil import tz
7
+ import time
8
 
9
  import gradio as gr
10
  import pandas as pd
 
60
 
61
 
62
  # -----------------------------
63
+ # Demo / Data loading with dynamic update
64
  # -----------------------------
65
+ def make_demo_dataframe(last_time=None) -> pd.DataFrame:
66
+ """隨機示範資料:含經緯度 + pid,模擬實時更新"""
67
+ if last_time is None:
68
+ last_time = datetime.now(tz=TAIPEI) - timedelta(minutes=60)
69
+ else:
70
+ last_time = last_time + timedelta(minutes=1) # 模擬每分鐘新增數據
71
+ times = [last_time + timedelta(minutes=i) for i in range(61)]
72
  amp = np.random.rand(len(times))
73
  cnt = np.random.randint(0, 11, size=len(times))
74
  lats = np.random.uniform(21.8, 25.3, size=len(times))
 
81
  "lon": lons
82
  })
83
  df["pid"] = np.arange(len(df))
84
+ return df, last_time
85
 
86
 
87
  def _finalize_time(df: pd.DataFrame) -> pd.DataFrame:
 
132
  raise ValueError(f"Google 連結載入失敗:{str(e)}")
133
 
134
 
135
+ def load_data(source: str, file: gr.File | None = None, sheet_url: str = "", last_time=None) -> tuple[pd.DataFrame, datetime]:
136
+ """依來源載入資料:demo / upload / drive,支援動態更新"""
137
  if source == "drive":
138
  if not sheet_url:
139
  raise ValueError("請選擇 Google 連結")
140
+ df = load_drive_csv(sheet_url)
141
+ return df, None
142
  elif source == "upload":
143
  if file is None:
144
  raise ValueError("請上傳 CSV 檔")
145
+ df = load_csv(file)
146
+ return df, None
147
  else:
148
+ return make_demo_dataframe(last_time)
149
 
150
 
151
  # -----------------------------
 
440
 
441
 
442
  # -----------------------------
443
+ # Main pipeline with dynamic update
444
  # -----------------------------
445
+ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, start_time, end_time, show_heatmap, last_time=None):
446
  try:
447
+ df, new_last_time = load_data(source, file, sheet_url, last_time)
448
  df = filter_data(df, start_time, end_time) # 新增過濾
449
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon", "pid"] and pd.api.types.is_numeric_dtype(df[c])]
450
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
 
471
  default_choice = point_choices[0] if point_choices else ""
472
  detail_df = pick_detail(df, default_choice)
473
 
474
+ demo_df = make_demo_dataframe()[0] # 只取 DataFrame 部分
475
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
476
  demo_df.to_csv(f, index=False)
477
  demo_csv_path = f.name
 
482
  demo_csv_path,
483
  gr.Dropdown(choices=point_choices, value=default_choice),
484
  detail_df,
485
+ "", # 錯誤訊息清空
486
+ new_last_time
487
  )
488
  except Exception as e:
489
  return (
 
492
  None,
493
  gr.Dropdown(choices=[], value=None),
494
  pd.DataFrame(),
495
+ str(e),
496
+ last_time
497
  )
498
 
499
 
500
+ def regenerate_demo(series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, current_choice, start_time, end_time, show_heatmap, last_time):
501
+ return pipeline("demo", None, "", series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice, start_time, end_time, show_heatmap, last_time)
502
 
503
 
504
  def update_detail(df: pd.DataFrame, choice: str):
 
506
 
507
 
508
  # -----------------------------
509
+ # UI 優化:添加動態更新按鈕和間隔更新
510
  # -----------------------------
511
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
512
+ gr.Markdown("## 動態時間序列 - Grafana-like Demo + Folium Map(支援 Google Drive / Sheets,新增熱圖與 Gauge)")
513
 
514
  with gr.Row():
515
  with gr.Column(scale=1):
 
539
 
540
  with gr.Row():
541
  run_btn = gr.Button("產生 Dashboard", scale=1)
542
+ update_btn = gr.Button("手動更新數據", scale=1)
543
+ interval = gr.Slider(5, 60, value=10, step=5, label="自動更新間隔 (秒)")
544
 
545
  error_msg = gr.Markdown(value="", label="錯誤訊息", visible=True)
546
 
 
577
  def probe_columns(source, file, preset_url, start_time, end_time):
578
  sheet_url = preset_url if source == "drive" else ""
579
  try:
580
+ df, _ = load_data(source, file, sheet_url)
581
  df = filter_data(df, start_time, end_time)
582
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon", "pid"] and pd.api.types.is_numeric_dtype(df[c])]
583
  return gr.CheckboxGroup(choices=numeric_cols, value=numeric_cols[:2]), df, ""
 
599
  json_box, json_file, df_view,
600
  demo_csv_file,
601
  point_selector, detail_view,
602
+ error_msg,
603
+ gr.State(value=None)
604
  ]
605
  )
606
 
607
+ # 產生 / 重新產生 / 動態更新
608
  run_btn.click(
609
  pipeline,
610
+ inputs=[source_radio, file_in, preset_dd, series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, start_time_in, end_time_in, heatmap_chk, gr.State(value=None)],
611
  outputs=[
612
  plot1, plot2, plot3, plot4, map_out,
613
  json_box, json_file, df_view,
614
  demo_csv_file,
615
  point_selector, detail_view,
616
+ error_msg,
617
+ gr.State(value=None)
618
  ]
619
  )
620
 
621
+ update_btn.click(
622
+ pipeline,
623
+ inputs=[source_radio, file_in, preset_dd, series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, start_time_in, end_time_in, heatmap_chk, gr.State()],
624
  outputs=[
625
  plot1, plot2, plot3, plot4, map_out,
626
  json_box, json_file, df_view,
627
  demo_csv_file,
628
  point_selector, detail_view,
629
+ error_msg,
630
+ gr.State()
631
  ]
632
  )
633
 
634
+ interval.change(
635
+ fn=lambda x: gr.update(),
636
+ inputs=[interval],
637
+ outputs=[]
638
+ )
639
+
640
+ demo.load(
641
+ None,
642
+ _js="""
643
+ () => {
644
+ function update() {
645
+ setTimeout(() => {
646
+ document.querySelector('button[aria-label="手動更新數據"]').click();
647
+ update();
648
+ }, interval * 1000);
649
+ }
650
+ const interval = """ + str(10) + """; // 初始間隔
651
+ update();
652
+ }
653
+ """,
654
+ _js_args=[interval]
655
+ )
656
+
657
  point_selector.change(
658
  update_detail,
659
  inputs=[df_view, point_selector],