cwadayi commited on
Commit
04f15f9
·
verified ·
1 Parent(s): b33c7b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -13
app.py CHANGED
@@ -14,9 +14,11 @@ import folium
14
  from matplotlib import cm
15
  import branca.colormap as bcm
16
  import folium.plugins as plugins
 
 
17
 
18
  from grafanalib.core import (
19
- Dashboard, Graph, Row, Target, YAxis, YAxes, Time
20
  )
21
 
22
  TAIPEI = tz.gettz("Asia/Taipei")
@@ -153,7 +155,7 @@ def filter_data(df: pd.DataFrame, start_time: str, end_time: str) -> pd.DataFram
153
 
154
 
155
  # -----------------------------
156
- # grafanalib JSON builder
157
  # -----------------------------
158
  def build_grafanalib_dashboard(series_columns: list[str], dual_axis: bool, rolling_window: int) -> dict:
159
  panels = []
@@ -192,6 +194,14 @@ def build_grafanalib_dashboard(series_columns: list[str], dual_axis: bool, rolli
192
  yAxes=YAxes(left=YAxis(format="short"), right=YAxis(format="short")),
193
  )
194
  )
 
 
 
 
 
 
 
 
195
  return Dashboard(
196
  title="Grafana-like Demo (grafanalib + Gradio)",
197
  rows=[Row(panels=panels)],
@@ -270,6 +280,76 @@ def render_rolling(df, col, window=5):
270
  return fig, df
271
 
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  # -----------------------------
274
  # Folium helpers (map + legend + heatmap)
275
  # -----------------------------
@@ -337,7 +417,7 @@ def make_point_choices(df: pd.DataFrame) -> list[str]:
337
  labels = []
338
  for _, r in df.iterrows():
339
  t = pd.to_datetime(r["time"]).strftime("%H:%M:%S")
340
- labels.append(f"#{int(r['pid'])} | {t} | amp={r['amplitude']:.3f} cnt={int(r['count'])}")
341
  return labels
342
 
343
 
@@ -376,11 +456,12 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
376
  fig1 = render_line(df, chosen[0])
377
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else plt.figure()
378
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], int(rolling_window))
 
379
 
380
  map_html = render_map_folium(df, value_col=chosen[0], size_col=chosen[1] if len(chosen) > 1 else "count",
381
  cmap_name=cmap_choice, tiles=tiles_choice, show_heatmap=bool(show_heatmap))
382
 
383
- point_choices = make_point_choices(df)
384
  default_choice = point_choices[0] if point_choices else ""
385
  detail_df = pick_detail(df, default_choice)
386
 
@@ -390,7 +471,7 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
390
  demo_csv_path = f.name
391
 
392
  return (
393
- fig1, fig2, fig3, map_html,
394
  dash_json_str, json_path, df_with_roll,
395
  demo_csv_path,
396
  gr.Dropdown(choices=point_choices, value=default_choice),
@@ -399,7 +480,7 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window,
399
  )
400
  except Exception as e:
401
  return (
402
- None, None, None, "<p>錯誤:無資料顯示</p>",
403
  "", None, pd.DataFrame(),
404
  None,
405
  gr.Dropdown(choices=[], value=None),
@@ -417,10 +498,10 @@ def update_detail(df: pd.DataFrame, choice: str):
417
 
418
 
419
  # -----------------------------
420
- # UI 優化:使用 Tab 分頁、添加錯誤顯示、時間過濾、熱圖選項
421
  # -----------------------------
422
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
423
- gr.Markdown("## 優化版 Grafana-like Demo + Folium Map(支援 Google Drive / Sheets,新增熱圖)")
424
 
425
  with gr.Row():
426
  with gr.Column(scale=1):
@@ -433,7 +514,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
433
  )
434
  with gr.Row():
435
  start_time_in = gr.Textbox(label="開始時間 (YYYY-MM-DD HH:MM:SS)", placeholder="2023-01-01 00:00:00")
436
- end_time_in = gr.Textbox(label="結束時間 (YYYY-MM-DD HH:59:59)", placeholder="2023-12-31 23:59:59")
437
 
438
  with gr.Column(scale=1):
439
  series_multiselect = gr.CheckboxGroup(label="數值欄位", choices=[])
@@ -459,7 +540,9 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
459
  with gr.Row():
460
  plot1 = gr.Plot(label="1:Line")
461
  plot2 = gr.Plot(label="2:Bar / Dual Axis")
 
462
  plot3 = gr.Plot(label="3:Rolling Mean")
 
463
 
464
  with gr.Tab("地圖"):
465
  map_out = gr.HTML(label="4:Geo Map (Interactive + Legend + Heatmap)")
@@ -473,7 +556,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
473
  df_view = gr.Dataframe(label="資料預覽(含 rolling)", wrap=True)
474
 
475
  with gr.Tab("點位詳情"):
476
- gr.Markdown("### 🔎 點位詳情(對應地圖彈窗中的 #ID")
477
  point_selector = gr.Dropdown(label="選擇點位(#ID | 時間 | 值)", choices=[], value=None)
478
  detail_view = gr.Dataframe(label="選取點詳細資料", wrap=True)
479
 
@@ -499,7 +582,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
499
  lambda: pipeline("drive", None, DRIVE_PRESETS[0], [], False, "5", "viridis", "OpenStreetMap", "", "", False),
500
  inputs=None,
501
  outputs=[
502
- plot1, plot2, plot3, map_out,
503
  json_box, json_file, df_view,
504
  demo_csv_file,
505
  point_selector, detail_view,
@@ -512,7 +595,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
512
  pipeline,
513
  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],
514
  outputs=[
515
- plot1, plot2, plot3, map_out,
516
  json_box, json_file, df_view,
517
  demo_csv_file,
518
  point_selector, detail_view,
@@ -524,7 +607,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
524
  regenerate_demo,
525
  inputs=[series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, point_selector, start_time_in, end_time_in, heatmap_chk],
526
  outputs=[
527
- plot1, plot2, plot3, map_out,
528
  json_box, json_file, df_view,
529
  demo_csv_file,
530
  point_selector, detail_view,
 
14
  from matplotlib import cm
15
  import branca.colormap as bcm
16
  import folium.plugins as plugins
17
+ from matplotlib.patches import Wedge, Rectangle, FancyArrowPatch
18
+ from matplotlib.path import Path as mpath
19
 
20
  from grafanalib.core import (
21
+ Dashboard, Graph, Row, Target, YAxis, YAxes, Time, BarGauge
22
  )
23
 
24
  TAIPEI = tz.gettz("Asia/Taipei")
 
155
 
156
 
157
  # -----------------------------
158
+ # grafanalib JSON builder (新增 BarGauge)
159
  # -----------------------------
160
  def build_grafanalib_dashboard(series_columns: list[str], dual_axis: bool, rolling_window: int) -> dict:
161
  panels = []
 
194
  yAxes=YAxes(left=YAxis(format="short"), right=YAxis(format="short")),
195
  )
196
  )
197
+ # 新增 BarGauge 面板,顯示最新值
198
+ panels.append(
199
+ BarGauge(
200
+ title=f"Latest {series_columns[0]}",
201
+ dataSource="(example)",
202
+ targets=[Target(expr=f"last({series_columns[0]})", legendFormat=series_columns[0])],
203
+ )
204
+ )
205
  return Dashboard(
206
  title="Grafana-like Demo (grafanalib + Gradio)",
207
  rows=[Row(panels=panels)],
 
280
  return fig, df
281
 
282
 
283
+ # -----------------------------
284
+ # 新增 Gauge 渲染 (使用 Matplotlib patches)
285
+ # -----------------------------
286
+ def degree_range(n):
287
+ start = np.linspace(0, 180, n + 1, endpoint=True)[0:-1]
288
+ end = np.linspace(0, 180, n + 1, endpoint=True)[1:]
289
+ mid_points = start + ((end - start) / 2.)
290
+ return np.c_[start, end], mid_points
291
+
292
+
293
+ def rot_text(ang):
294
+ rotation = np.degrees(np.radians(ang) * np.pi / np.pi - np.radians(90))
295
+ return rotation
296
+
297
+
298
+ def render_gauge(df, col):
299
+ value = df[col].iloc[-1] if not df.empty else 0
300
+ min_val, max_val = df[col].min(), df[col].max()
301
+ normalized = (value - min_val) / (max_val - min_val + 1e-9) if max_val > min_val else 0
302
+
303
+ labels = ['LOW', 'MEDIUM', 'HIGH']
304
+ N = len(labels)
305
+ colors = ['#007A00', '#FFCC00', '#ED1C24'] # Green, Yellow, Red
306
+
307
+ if normalized < 0.33:
308
+ arrow = 1
309
+ elif normalized < 0.66:
310
+ arrow = 2
311
+ else:
312
+ arrow = 3
313
+
314
+ fig, ax = plt.subplots(figsize=(6, 4))
315
+
316
+ ang_range, mid_points = degree_range(N)
317
+
318
+ labels = labels[::-1]
319
+
320
+ patches = []
321
+ for ang, c in zip(ang_range, colors):
322
+ patches.append(Wedge((0., 0.), .4, *ang, facecolor='w', lw=2))
323
+ patches.append(Wedge((0., 0.), .4, *ang, width=0.10, facecolor=c, lw=2, alpha=0.5))
324
+
325
+ [ax.add_patch(p) for p in patches]
326
+
327
+ for mid, lab in zip(mid_points, labels):
328
+ ax.text(0.35 * np.cos(np.radians(mid)), 0.35 * np.sin(np.radians(mid)), lab,
329
+ horizontalalignment='center', verticalalignment='center', fontsize=14,
330
+ fontweight='bold', rotation=rot_text(mid))
331
+
332
+ r = Rectangle((-0.4, -0.1), 0.8, 0.1, facecolor='w', lw=2)
333
+ ax.add_patch(r)
334
+
335
+ ax.text(0, -0.05, f"Latest {col}: {value:.2f}", horizontalalignment='center',
336
+ verticalalignment='center', fontsize=14, fontweight='bold')
337
+
338
+ pos = mid_points[abs(arrow - N)]
339
+ ax.arrow(0, 0, 0.225 * np.cos(np.radians(pos)), 0.225 * np.sin(np.radians(pos)),
340
+ width=0.04, head_width=0.09, head_length=0.1, fc='k', ec='k')
341
+
342
+ ax.add_patch(FancyArrowPatch((0, 0), (0.01 * np.cos(np.radians(pos)), 0.01 * np.sin(np.radians(pos))),
343
+ mutation_scale=10, fc='k', ec='k'))
344
+
345
+ ax.set_frame_on(False)
346
+ ax.axes.set_xticks([])
347
+ ax.axes.set_yticks([])
348
+ ax.axis('equal')
349
+ plt.tight_layout()
350
+ return fig
351
+
352
+
353
  # -----------------------------
354
  # Folium helpers (map + legend + heatmap)
355
  # -----------------------------
 
417
  labels = []
418
  for _, r in df.iterrows():
419
  t = pd.to_datetime(r["time"]).strftime("%H:%M:%S")
420
+ labels.append(f"#{int(r['pid'])} | {t} | amp={r.get('amplitude', 0):.3f} cnt={int(r.get('count', 0))}")
421
  return labels
422
 
423
 
 
456
  fig1 = render_line(df, chosen[0])
457
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else plt.figure()
458
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], int(rolling_window))
459
+ fig4 = render_gauge(df, chosen[0])
460
 
461
  map_html = render_map_folium(df, value_col=chosen[0], size_col=chosen[1] if len(chosen) > 1 else "count",
462
  cmap_name=cmap_choice, tiles=tiles_choice, show_heatmap=bool(show_heatmap))
463
 
464
+ point_choices = [] if show_heatmap else make_point_choices(df)
465
  default_choice = point_choices[0] if point_choices else ""
466
  detail_df = pick_detail(df, default_choice)
467
 
 
471
  demo_csv_path = f.name
472
 
473
  return (
474
+ fig1, fig2, fig3, fig4, map_html,
475
  dash_json_str, json_path, df_with_roll,
476
  demo_csv_path,
477
  gr.Dropdown(choices=point_choices, value=default_choice),
 
480
  )
481
  except Exception as e:
482
  return (
483
+ None, None, None, None, "<p>錯誤:無資料顯示</p>",
484
  "", None, pd.DataFrame(),
485
  None,
486
  gr.Dropdown(choices=[], value=None),
 
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):
 
514
  )
515
  with gr.Row():
516
  start_time_in = gr.Textbox(label="開始時間 (YYYY-MM-DD HH:MM:SS)", placeholder="2023-01-01 00:00:00")
517
+ end_time_in = gr.Textbox(label="結束時間 (YYYY-MM-DD HH:MM:SS)", placeholder="2023-12-31 23:59:59")
518
 
519
  with gr.Column(scale=1):
520
  series_multiselect = gr.CheckboxGroup(label="數值欄位", choices=[])
 
540
  with gr.Row():
541
  plot1 = gr.Plot(label="1:Line")
542
  plot2 = gr.Plot(label="2:Bar / Dual Axis")
543
+ with gr.Row():
544
  plot3 = gr.Plot(label="3:Rolling Mean")
545
+ plot4 = gr.Plot(label="4:Gauge")
546
 
547
  with gr.Tab("地圖"):
548
  map_out = gr.HTML(label="4:Geo Map (Interactive + Legend + Heatmap)")
 
556
  df_view = gr.Dataframe(label="資料預覽(含 rolling)", wrap=True)
557
 
558
  with gr.Tab("點位詳情"):
559
+ gr.Markdown("### 🔎 點位詳情(對應地圖彈窗中的 #ID,熱圖模式下不可用)")
560
  point_selector = gr.Dropdown(label="選擇點位(#ID | 時間 | 值)", choices=[], value=None)
561
  detail_view = gr.Dataframe(label="選取點詳細資料", wrap=True)
562
 
 
582
  lambda: pipeline("drive", None, DRIVE_PRESETS[0], [], False, "5", "viridis", "OpenStreetMap", "", "", False),
583
  inputs=None,
584
  outputs=[
585
+ plot1, plot2, plot3, plot4, map_out,
586
  json_box, json_file, df_view,
587
  demo_csv_file,
588
  point_selector, detail_view,
 
595
  pipeline,
596
  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],
597
  outputs=[
598
+ plot1, plot2, plot3, plot4, map_out,
599
  json_box, json_file, df_view,
600
  demo_csv_file,
601
  point_selector, detail_view,
 
607
  regenerate_demo,
608
  inputs=[series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd, point_selector, start_time_in, end_time_in, heatmap_chk],
609
  outputs=[
610
+ plot1, plot2, plot3, plot4, map_out,
611
  json_box, json_file, df_view,
612
  demo_csv_file,
613
  point_selector, detail_view,