cwadayi commited on
Commit
c9f8787
·
verified ·
1 Parent(s): 319229a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +34 -13
app.py CHANGED
@@ -10,6 +10,7 @@ import numpy as np
10
  import matplotlib.dates as mdates
11
  import folium
12
  from matplotlib import cm
 
13
 
14
  from grafanalib.core import (
15
  Dashboard, Graph, Row, Target, YAxis, YAxes, Time
@@ -208,13 +209,25 @@ def _to_hex_color(value: float, cmap=cm.viridis) -> str:
208
  return "#{:02x}{:02x}{:02x}".format(int(rgba[0]*255), int(rgba[1]*255), int(rgba[2]*255))
209
 
210
 
211
- def render_map_folium(df: pd.DataFrame, value_col: str = "amplitude", size_col: str = "count") -> str:
212
- """用 folium 建立互動地圖,回傳 HTML"""
 
 
213
  center_lat, center_lon = df["lat"].mean(), df["lon"].mean()
214
- m = folium.Map(location=[center_lat, center_lon], zoom_start=7)
215
 
216
  vmin, vmax = df[value_col].min(), df[value_col].max()
 
217
 
 
 
 
 
 
 
 
 
 
218
  for _, row in df.iterrows():
219
  norm_val = (row[value_col] - vmin) / (vmax - vmin + 1e-9)
220
  folium.CircleMarker(
@@ -224,7 +237,7 @@ def render_map_folium(df: pd.DataFrame, value_col: str = "amplitude", size_col:
224
  weight=1,
225
  fill=True,
226
  fill_opacity=0.7,
227
- fill_color=_to_hex_color(norm_val),
228
  popup=f"{value_col}: {row[value_col]:.2f}, {size_col}: {row[size_col]}"
229
  ).add_to(m)
230
 
@@ -234,7 +247,7 @@ def render_map_folium(df: pd.DataFrame, value_col: str = "amplitude", size_col:
234
  # -----------------------------
235
  # Main pipeline
236
  # -----------------------------
237
- def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window):
238
  df = load_data(source, file, sheet_url)
239
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon"] and pd.api.types.is_numeric_dtype(df[c])]
240
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
@@ -251,7 +264,8 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window):
251
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else plt.figure()
252
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], int(rolling_window))
253
 
254
- map_html = render_map_folium(df, value_col=chosen[0], size_col="count")
 
255
 
256
  demo_df = make_demo_dataframe()
257
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
@@ -261,15 +275,15 @@ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window):
261
  return fig1, fig2, fig3, map_html, dash_json_str, json_path, df_with_roll, demo_csv_path
262
 
263
 
264
- def regenerate_demo(series_choice, dual_axis, rolling_window):
265
- return pipeline("demo", None, "", series_choice, dual_axis, rolling_window)
266
 
267
 
268
  # -----------------------------
269
  # UI
270
  # -----------------------------
271
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
272
- gr.Markdown("## Grafana-like Demo + Folium Map")
273
 
274
  source_radio = gr.Radio(["upload", "drive", "demo"], label="資料來源", value="demo")
275
  file_in = gr.File(label="上傳 CSV(僅在選擇 upload 時使用)", file_types=[".csv"])
@@ -278,6 +292,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
278
  series_multiselect = gr.CheckboxGroup(label="數值欄位", choices=[])
279
  dual_axis_chk = gr.Checkbox(label="第二面板啟用雙軸", value=False)
280
  rolling_dd = gr.Dropdown(label="Rolling window", choices=["3", "5", "10", "20"], value="5")
 
 
 
 
 
 
 
281
 
282
  run_btn = gr.Button("產生 Dashboard")
283
  regen_btn = gr.Button("🔁 重新產生示範資料")
@@ -285,7 +306,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
285
  plot1 = gr.Plot(label="1:Line")
286
  plot2 = gr.Plot(label="2:Bar / Dual Axis")
287
  plot3 = gr.Plot(label="3:Rolling Mean")
288
- map_out = gr.HTML(label="4:Geo Map (Interactive)")
289
 
290
  json_box = gr.Code(label="grafanalib Dashboard JSON", language="json")
291
  json_file = gr.File(label="下載 dashboard.json")
@@ -302,20 +323,20 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
302
  sheet_url_box.change(probe_columns, inputs=[source_radio, file_in, sheet_url_box], outputs=[series_multiselect, df_view])
303
 
304
  demo.load(
305
- lambda: pipeline("demo", None, "", [], False, "5"),
306
  inputs=None,
307
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
308
  )
309
 
310
  run_btn.click(
311
  pipeline,
312
- inputs=[source_radio, file_in, sheet_url_box, series_multiselect, dual_axis_chk, rolling_dd],
313
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
314
  )
315
 
316
  regen_btn.click(
317
  regenerate_demo,
318
- inputs=[series_multiselect, dual_axis_chk, rolling_dd],
319
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
320
  )
321
 
 
10
  import matplotlib.dates as mdates
11
  import folium
12
  from matplotlib import cm
13
+ import branca.colormap as bcm
14
 
15
  from grafanalib.core import (
16
  Dashboard, Graph, Row, Target, YAxis, YAxes, Time
 
209
  return "#{:02x}{:02x}{:02x}".format(int(rgba[0]*255), int(rgba[1]*255), int(rgba[2]*255))
210
 
211
 
212
+ def render_map_folium(df: pd.DataFrame, value_col: str = "amplitude",
213
+ size_col: str = "count", cmap_name: str = "viridis",
214
+ tiles: str = "OpenStreetMap") -> str:
215
+ """用 folium 建立互動地圖,帶顏色圖例"""
216
  center_lat, center_lon = df["lat"].mean(), df["lon"].mean()
217
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=7, tiles=tiles)
218
 
219
  vmin, vmax = df[value_col].min(), df[value_col].max()
220
+ cmap = getattr(cm, cmap_name)
221
 
222
+ # 建立 folium colormap (legend)
223
+ colormap = bcm.LinearColormap(
224
+ [ _to_hex_color(i, cmap) for i in np.linspace(0, 1, 256) ],
225
+ vmin=vmin, vmax=vmax
226
+ )
227
+ colormap.caption = f"{value_col} (color scale)"
228
+ colormap.add_to(m)
229
+
230
+ # 加點資料
231
  for _, row in df.iterrows():
232
  norm_val = (row[value_col] - vmin) / (vmax - vmin + 1e-9)
233
  folium.CircleMarker(
 
237
  weight=1,
238
  fill=True,
239
  fill_opacity=0.7,
240
+ fill_color=_to_hex_color(norm_val, cmap),
241
  popup=f"{value_col}: {row[value_col]:.2f}, {size_col}: {row[size_col]}"
242
  ).add_to(m)
243
 
 
247
  # -----------------------------
248
  # Main pipeline
249
  # -----------------------------
250
+ def pipeline(source, file, sheet_url, series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice):
251
  df = load_data(source, file, sheet_url)
252
  numeric_cols = [c for c in df.columns if c not in ["time", "lat", "lon"] and pd.api.types.is_numeric_dtype(df[c])]
253
  chosen = [c for c in (series_choice or numeric_cols[:2]) if c in numeric_cols]
 
264
  fig2 = render_bar_or_dual(df, chosen[1], chosen[0], bool(dual_axis)) if len(chosen) > 1 else plt.figure()
265
  fig3, df_with_roll = render_rolling(df.copy(), chosen[0], int(rolling_window))
266
 
267
+ map_html = render_map_folium(df, value_col=chosen[0], size_col="count",
268
+ cmap_name=cmap_choice, tiles=tiles_choice)
269
 
270
  demo_df = make_demo_dataframe()
271
  with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="w", encoding="utf-8") as f:
 
275
  return fig1, fig2, fig3, map_html, dash_json_str, json_path, df_with_roll, demo_csv_path
276
 
277
 
278
+ def regenerate_demo(series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice):
279
+ return pipeline("demo", None, "", series_choice, dual_axis, rolling_window, cmap_choice, tiles_choice)
280
 
281
 
282
  # -----------------------------
283
  # UI
284
  # -----------------------------
285
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
286
+ gr.Markdown("## Grafana-like Demo + Folium Map + Colormap + Tiles + Legend")
287
 
288
  source_radio = gr.Radio(["upload", "drive", "demo"], label="資料來源", value="demo")
289
  file_in = gr.File(label="上傳 CSV(僅在選擇 upload 時使用)", file_types=[".csv"])
 
292
  series_multiselect = gr.CheckboxGroup(label="數值欄位", choices=[])
293
  dual_axis_chk = gr.Checkbox(label="第二面板啟用雙軸", value=False)
294
  rolling_dd = gr.Dropdown(label="Rolling window", choices=["3", "5", "10", "20"], value="5")
295
+ cmap_dd = gr.Dropdown(label="地圖配色 (colormap)",
296
+ choices=["viridis", "plasma", "inferno", "magma", "cividis", "coolwarm"],
297
+ value="viridis")
298
+ tiles_dd = gr.Dropdown(label="地圖底圖 (tiles)",
299
+ choices=["OpenStreetMap", "Stamen Terrain", "Stamen Toner",
300
+ "CartoDB positron", "CartoDB dark_matter"],
301
+ value="OpenStreetMap")
302
 
303
  run_btn = gr.Button("產生 Dashboard")
304
  regen_btn = gr.Button("🔁 重新產生示範資料")
 
306
  plot1 = gr.Plot(label="1:Line")
307
  plot2 = gr.Plot(label="2:Bar / Dual Axis")
308
  plot3 = gr.Plot(label="3:Rolling Mean")
309
+ map_out = gr.HTML(label="4:Geo Map (Interactive + Legend)")
310
 
311
  json_box = gr.Code(label="grafanalib Dashboard JSON", language="json")
312
  json_file = gr.File(label="下載 dashboard.json")
 
323
  sheet_url_box.change(probe_columns, inputs=[source_radio, file_in, sheet_url_box], outputs=[series_multiselect, df_view])
324
 
325
  demo.load(
326
+ lambda: pipeline("demo", None, "", [], False, "5", "viridis", "OpenStreetMap"),
327
  inputs=None,
328
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
329
  )
330
 
331
  run_btn.click(
332
  pipeline,
333
+ inputs=[source_radio, file_in, sheet_url_box, series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd],
334
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
335
  )
336
 
337
  regen_btn.click(
338
  regenerate_demo,
339
+ inputs=[series_multiselect, dual_axis_chk, rolling_dd, cmap_dd, tiles_dd],
340
  outputs=[plot1, plot2, plot3, map_out, json_box, json_file, df_view, demo_csv_file]
341
  )
342