Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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",
|
| 212 |
-
|
|
|
|
|
|
|
| 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 |
|