cwadayi commited on
Commit
bd86a2f
·
verified ·
1 Parent(s): 91725da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -8
app.py CHANGED
@@ -1,14 +1,205 @@
 
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import matplotlib.pyplot as plt
4
 
5
- def show_chart():
6
- df = pd.DataFrame({"time": range(10), "value": [i**0.5 for i in range(10)]})
7
- plt.plot(df["time"], df["value"])
8
- return plt.gcf()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- with gr.Blocks() as demo:
11
- gr.Markdown("### 模擬 Grafana Dashboard")
12
- gr.Plot(show_chart)
 
 
13
 
14
- demo.launch()
 
 
1
+ import io
2
+ import json
3
+ from datetime import datetime, timedelta
4
+ from dateutil import tz
5
+
6
  import gradio as gr
7
  import pandas as pd
8
  import matplotlib.pyplot as plt
9
 
10
+ # === grafanalib: 定義 Dashboard 結構(不負責畫圖) ===
11
+ # 注意:grafanalib 只產生 Grafana JSON;我們會把 JSON 顯示出來,並用 Matplotlib 實際畫圖
12
+ from grafanalib.core import (
13
+ Dashboard, Graph, Row, Target, YAxis, YAxes, OPS_FORMAT,
14
+ )
15
+
16
+ TAIPEI = tz.gettz("Asia/Taipei")
17
+
18
+
19
+ def make_demo_dataframe():
20
+ """產生示範資料(10 分鐘 1 點),欄位:time, amplitude, count"""
21
+ t0 = datetime.now(tz=TAIPEI) - timedelta(minutes=60)
22
+ times = [t0 + timedelta(minutes=i) for i in range(61)]
23
+ # 假資料:振幅、事件數
24
+ amp = [0.5 + 0.4 * __import__("math").sin(i / 6.0) for i in range(61)]
25
+ cnt = [max(0, int(5 + 3 * __import__("math").cos(i / 10.0))) for i in range(61)]
26
+ df = pd.DataFrame({"time": times, "amplitude": amp, "count": cnt})
27
+ return df
28
+
29
+
30
+ def load_csv(file: gr.File | None) -> pd.DataFrame:
31
+ """支援使用者上傳 CSV,要求至少包含 time 欄位;其他欄位可自訂"""
32
+ if file is None:
33
+ return make_demo_dataframe()
34
+
35
+ # 讀 CSV,嘗試自動 parse 日期欄
36
+ df = pd.read_csv(file.name)
37
+ # 嘗試找到時間欄位(常見命名)
38
+ time_col_candidates = ["time", "timestamp", "datetime", "date"]
39
+ time_col = None
40
+ for c in time_col_candidates:
41
+ if c in df.columns:
42
+ time_col = c
43
+ break
44
+ if time_col is None:
45
+ raise ValueError("CSV 檔需包含時間欄位,例如:time / timestamp / datetime / date")
46
+
47
+ df[time_col] = pd.to_datetime(df[time_col])
48
+ df.rename(columns={time_col: "time"}, inplace=True)
49
+ # 保證有時區(預設轉為台北)
50
+ if df["time"].dt.tz is None:
51
+ df["time"] = df["time"].dt.tz_localize(TAIPEI)
52
+ return df
53
+
54
+
55
+ def build_grafanalib_dashboard(series_columns: list[str]) -> dict:
56
+ """
57
+ 用 grafanalib 定義一個簡單 Dashboard:
58
+ - Row 1:Time Series(Graph)展示第一個數值欄
59
+ - Row 2:Event Count(Graph)展示第二個數值欄(如有)
60
+ 注意:Target 只是示意;實際取數由我們在 Gradio 端處理。
61
+ """
62
+ # 目標(在 Grafana 會對應到資料來源查詢;這裡只作為 JSON 示意)
63
+ targets = [
64
+ Target(expr=f"{series_columns[0]}", legendFormat=series_columns[0]),
65
+ ]
66
+ panels = [
67
+ Graph(
68
+ title=f"Time Series - {series_columns[0]}",
69
+ dataSource="(example)", # 在真實 Grafana 才會用到
70
+ targets=targets,
71
+ yAxes=YAxes(
72
+ left=YAxis(format=OPS_FORMAT.NONE),
73
+ right=YAxis(format=OPS_FORMAT.NONE),
74
+ ),
75
+ )
76
+ ]
77
+
78
+ if len(series_columns) > 1:
79
+ panels.append(
80
+ Graph(
81
+ title=f"Event Count - {series_columns[1]}",
82
+ dataSource="(example)",
83
+ targets=[Target(expr=f"{series_columns[1]}", legendFormat=series_columns[1])],
84
+ yAxes=YAxes(
85
+ left=YAxis(format=OPS_FORMAT.NONE),
86
+ right=YAxis(format=OPS_FORMAT.NONE),
87
+ ),
88
+ )
89
+ )
90
+
91
+ dash = Dashboard(
92
+ title="Seismology Demo Dashboard (grafanalib + Gradio)",
93
+ rows=[Row(panels=panels)],
94
+ timezone="browser",
95
+ time_from="now-1h",
96
+ time_to="now",
97
+ )
98
+ return dash.to_json_data()
99
+
100
+
101
+ def render_matplotlib(df: pd.DataFrame, value_cols: list[str]):
102
+ """
103
+ 用 Matplotlib 依據 df 實際畫圖,對應 grafanalib 定義的欄位。
104
+ 回傳多個 matplotlib Figure 物件(給 gr.Gallery / gr.Plot 使用)
105
+ """
106
+ figs = []
107
+
108
+ # 第一張圖
109
+ fig1 = plt.figure()
110
+ plt.plot(df["time"], df[value_cols[0]])
111
+ plt.title(f"Time Series - {value_cols[0]}")
112
+ plt.xlabel("Time")
113
+ plt.ylabel(value_cols[0])
114
+ plt.xticks(rotation=20)
115
+ plt.tight_layout()
116
+ figs.append(fig1)
117
+
118
+ # 第二張圖(選配)
119
+ if len(value_cols) > 1:
120
+ fig2 = plt.figure()
121
+ plt.plot(df["time"], df[value_cols[1]])
122
+ plt.title(f"Event Count - {value_cols[1]}")
123
+ plt.xlabel("Time")
124
+ plt.ylabel(value_cols[1])
125
+ plt.xticks(rotation=20)
126
+ plt.tight_layout()
127
+ figs.append(fig2)
128
+
129
+ return figs
130
+
131
+
132
+ def pipeline(file, series_choice):
133
+ """
134
+ 主要管線:
135
+ 1) 讀 CSV(或用示範資料)
136
+ 2) 決定要展示的欄位
137
+ 3) 用 grafanalib 產出 Dashboard JSON
138
+ 4) 用 Matplotlib 畫圖
139
+ """
140
+ df = load_csv(file)
141
+
142
+ # 自動找出數值欄位(排除 time)
143
+ numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
144
+ if not numeric_cols:
145
+ raise ValueError("未找到可繪圖的���值欄位。請在 CSV 中提供至少一個數值欄位(除了 time 以外)。")
146
+
147
+ # 若使用者有選擇,以選擇優先;否則用前兩個
148
+ chosen = series_choice or numeric_cols[:2]
149
+ chosen = [c for c in chosen if c in numeric_cols]
150
+ if not chosen:
151
+ chosen = numeric_cols[:2]
152
+
153
+ dash_json = build_grafanalib_dashboard(chosen)
154
+ figs = render_matplotlib(df, chosen)
155
+
156
+ # 輸出 JSON(pretty)
157
+ dash_json_str = json.dumps(dash_json, ensure_ascii=False, indent=2, default=str)
158
+
159
+ # 讓使用者可以下載 JSON
160
+ json_bytes = io.BytesIO(dash_json_str.encode("utf-8"))
161
+ json_bytes.name = "dashboard.json"
162
+
163
+ return figs, dash_json_str, json_bytes, df
164
+
165
+
166
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
167
+ gr.Markdown("# grafanalib 生成 Dashboard + Gradio 呈現\n"
168
+ "上傳 CSV(需含 `time` 欄)或用示範資料,選擇要畫的數值欄位。\n\n"
169
+ "**說明**:grafanalib 負責產生 Grafana 的 Dashboard JSON 結構;下方 Matplotlib 圖是我們在 Space 端實際繪圖的呈現。")
170
+
171
+ with gr.Row():
172
+ file_in = gr.File(label="上傳 CSV(可空,會用示範資料)", file_types=[".csv"])
173
+ series_multiselect = gr.CheckboxGroup(label="選擇要呈現的數值欄位(不選則自動挑前兩個)", choices=[])
174
+
175
+ run_btn = gr.Button("產生 Dashboard 並繪圖")
176
+
177
+ with gr.Row():
178
+ gallery = gr.Gallery(label="圖表預覽(Matplotlib)", height=400)
179
+ with gr.Row():
180
+ json_box = gr.Code(label="grafanalib 輸出的 Dashboard JSON(可匯入真正的 Grafana)", language="json")
181
+ with gr.Row():
182
+ json_file = gr.File(label="下載 dashboard.json")
183
+
184
+ # 顯示 DataFrame(互動檢視)
185
+ df_view = gr.Dataframe(label="資料預覽(自動偵測 time + 數值欄位)", wrap=True)
186
+
187
+ def probe_columns(file):
188
+ df = load_csv(file)
189
+ numeric_cols = [c for c in df.columns if c != "time" and pd.api.types.is_numeric_dtype(df[c])]
190
+ return gr.CheckboxGroup(choices=numeric_cols), df
191
+
192
+ file_in.change(
193
+ probe_columns,
194
+ inputs=[file_in],
195
+ outputs=[series_multiselect, df_view],
196
+ )
197
 
198
+ run_btn.click(
199
+ pipeline,
200
+ inputs=[file_in, series_multiselect],
201
+ outputs=[gallery, json_box, json_file, df_view],
202
+ )
203
 
204
+ if __name__ == "__main__":
205
+ demo.launch()