protoai-dashboard / plot_utils.py
dcarreradigm's picture
Upload folder using huggingface_hub
26c3893 verified
Raw
History Blame Contribute Delete
15.6 kB
import io, base64,os,uuid
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cm
import matplotlib.gridspec as gridspec
import geopandas as gpd
import pandas as pd
def show_two_plots(gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False):
#dynamically change marker size
n_points = len(gdf)
marker_size = 8500 / n_points
marker_size = max(2, min(marker_size, 200))
filter_marker_size = marker_size*0.6
print(marker_size)
print('split', split)
if gdf.empty:
raise ValueError("GeoDataFrame is empty")
required_cols = [name1, name2]
for col in required_cols:
if col not in gdf.columns:
raise ValueError(f"Column '{col}' not found in gdf")
#Create a shared colormap and normalization
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin=vmin, vmax=vmax)
# Create subplots
fig = plt.figure(figsize=(12, 6))
gs = gridspec.GridSpec(1, 3, width_ratios=[1, 1, 0.05], wspace=0.3)
axes = [fig.add_subplot(gs[0]), fig.add_subplot(gs[1]), fig.add_subplot(gs[2]) ]
if split == False:
# First plot
gdf.plot(column=name1, cmap=cmap, norm=norm, ax=axes[0], marker='s', markersize=marker_size)
else:
# Split data into three GeoDataFrames
# below_100 = gdf[gdf[split] <= 100]
above_capacity = gdf[gdf[split] > (100-gdf['Pct_Construccion'])]
above_100 = gdf[gdf[split] > 100]
above_colors = {"capacity":"chocolate", "100":"firebrick"}
# Plot values ≤ 100 using colormap
gdf.plot(column=name1, cmap=colorscheme, ax=axes[0], vmin=vmin, vmax=vmax, marker='s', markersize=marker_size)
# Plot values > capacity in orange
above_capacity.plot(color=above_colors['capacity'], ax=axes[0], label='> capacidad debido a la construcción', marker='x', markersize=filter_marker_size)
# Plot values > 100 in red
above_100.plot(color=above_colors['100'], ax=axes[0], label='> 100% cobertura arbórea', marker='x', markersize=filter_marker_size)
# Add legend manually for orange points
orange_patch = plt.Line2D([0], [0], marker='o', color='w', label='> capacidad debido a la construcción',
markerfacecolor=above_colors['capacity'], markersize=8)
# Add legend manually for red points
red_patch = plt.Line2D([0], [0], marker='o', color='w', label='> 100% cobertura arbórea',
markerfacecolor=above_colors['100'], markersize=8)
axes[0].legend(handles=[orange_patch, red_patch])
# axes[0].set_title(title1)
axes[0].set_title(title1, pad=-30)
axes[0].set_axis_off()
# Second plot (original)
gdf.plot(column=name2, cmap=cmap, norm=norm, ax=axes[1],marker='s', markersize=marker_size)
axes[1].set_title(title2, pad=-30)
axes[1].set_axis_off()
# Shared colorbar
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = [] # Dummy array for the colormap
cbar = fig.colorbar(sm, cax=axes[2], orientation='vertical', fraction=0.03, pad=0.02)
cbar.set_label(label)
return fig
def show_one_plot(gdf, name, vmin, vmax, colorscheme, title, label):
#dynamically change marker size
n_points = len(gdf)
marker_size = 8500 / n_points
marker_size = max(2, min(marker_size, 200))
# Second set of plots
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin, vmax)
# Create subplots
fig = plt.figure(figsize=(12, 6))
gs = gridspec.GridSpec(1, 2, width_ratios=[1, 0.025], wspace=0.3)
axes = [fig.add_subplot(gs[0]), fig.add_subplot(gs[1])]
gdf.plot(column=name, cmap=cmap, norm=norm, ax=axes[0], marker='s', markersize=marker_size)
axes[0].set_title(title, pad=-30)
axes[0].set_axis_off()
# Add a colorbar
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = [] # Dummy data for the colorbar
cbar = fig.colorbar(sm, cax=axes[1], orientation='vertical', fraction=0.03, pad=0.02)
cbar.set_label(label)
return fig
def show_two_plots_base64(
gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False
):
"""Genera dos mapas (antes/después) de 1024×1024 px y los devuelve en base64."""
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin=vmin, vmax=vmax)
# Tamaño fijo
fig_size_inch = (5.12, 5.12)
dpi_val = 200
# Tamaño de marcador dinámico
n_points = len(gdf)
marker_size = 8500 / n_points
marker_size = max(2, min(marker_size, 200))
filter_marker_size = marker_size * 0.6
def fig_to_base64(fig):
buf = io.BytesIO()
fig.savefig(buf, format="png", bbox_inches="tight", dpi=dpi_val)
buf.seek(0)
return base64.b64encode(buf.read()).decode("utf-8")
def make_fig(column, title, apply_split=False):
# Fijamos tamaño físico y DPI
fig, ax = plt.subplots(figsize=fig_size_inch, dpi=dpi_val)
# Mapa base
gdf.plot(
column=column, cmap=cmap, norm=norm,
ax=ax, marker='s', markersize=marker_size
)
# Aplicar split sólo al mapa “después”
if apply_split and split:
above_capacity = gdf[gdf[split] > (100 - gdf["Pct_Construccion"])]
above_100 = gdf[gdf[split] > 100]
above_colors = {"capacity": "chocolate", "100": "firebrick"}
above_capacity.plot(
color=above_colors["capacity"], ax=ax,
marker='x', markersize=filter_marker_size
)
above_100.plot(
color=above_colors["100"], ax=ax,
marker='x', markersize=filter_marker_size
)
# Leyenda
orange_patch = plt.Line2D(
[0], [0], marker='o', color='w',
label='> capacidad (construcción)',
markerfacecolor=above_colors['capacity'], markersize=8
)
red_patch = plt.Line2D(
[0], [0], marker='o', color='w',
label='> 100% cobertura arbórea',
markerfacecolor=above_colors['100'], markersize=8
)
ax.legend(handles=[orange_patch, red_patch])
ax.set_title(title, pad=-25)
ax.set_axis_off()
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = []
fig.colorbar(
sm, ax=ax, orientation='vertical',
fraction=0.03, pad=0.02
).set_label(label)
return fig
# “Después” (aplica split)
fig_after = make_fig(name1, title1, apply_split=True)
# “Antes” (sin split)
fig_before = make_fig(name2, title2, apply_split=False)
return fig_to_base64(fig_after), fig_to_base64(fig_before)
def show_two_plots_base64_clean(
gdf, name1, name2, vmin, vmax, colorscheme,
title1, title2, label, split=False
):
"""
Genera dos mapas (antes/después) de 1024×1024 px.
Cada mapa incluye su colorbar, pero no título ni leyenda.
Devuelve dict con imágenes base64, títulos y leyenda HTML aparte.
"""
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin=vmin, vmax=vmax)
fig_size_inch = (5.12, 5.12)
dpi_val = 200
n_points = len(gdf)
marker_size = 8500 / n_points
marker_size = max(2, min(marker_size, 200))
filter_marker_size = marker_size * 0.6
def fig_to_base64(fig):
buf = io.BytesIO()
fig.savefig(buf, format="png", bbox_inches="tight", dpi=dpi_val)
buf.seek(0)
return base64.b64encode(buf.read()).decode("utf-8")
def make_fig(column, apply_split=False):
fig, ax = plt.subplots(figsize=fig_size_inch, dpi=dpi_val)
# Base plot
gdf.plot(
column=column, cmap=cmap, norm=norm,
ax=ax, marker='s', markersize=marker_size
)
# Split solo en el "después"
if apply_split and split:
above_capacity = gdf[gdf[split] > (100 - gdf["Pct_Construccion"])]
above_100 = gdf[gdf[split] > 100]
above_colors = {"capacity": "chocolate", "100": "firebrick"}
above_capacity.plot(
color=above_colors["capacity"], ax=ax,
marker='x', markersize=filter_marker_size
)
above_100.plot(
color=above_colors["100"], ax=ax,
marker='x', markersize=filter_marker_size
)
# Quitar ejes y agregar colorbar
ax.set_axis_off()
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = []
fig.colorbar(
sm, ax=ax, orientation='vertical',
fraction=0.03, pad=0.02
).set_label(label)
plt.tight_layout()
return fig
# Generar ambas figuras
fig_after = make_fig(name1, apply_split=True)
fig_before = make_fig(name2, apply_split=False)
# Convertir ambas a base64
img_after = fig_to_base64(fig_after)
img_before = fig_to_base64(fig_before)
# Leyenda HTML (solo si split activo)
legend_html = ""
if split:
legend_html = """
<div class='flex flex-col text-sm mt-2'>
<div class='flex items-center space-x-2'>
<span class='inline-block w-3 h-3 rounded-full' style='background-color:chocolate'></span>
<span>&gt; capacidad (por construcción)</span>
</div>
<div class='flex items-center space-x-2'>
<span class='inline-block w-3 h-3 rounded-full' style='background-color:firebrick'></span>
<span>&gt; 100% cobertura arbórea</span>
</div>
</div>
"""
return {
"img_before": img_before,
"img_after": img_after,
"title_before": title2,
"title_after": title1,
"legend_html": legend_html
}
def show_two_plots_and_export(
gdf, name1, name2, vmin, vmax, colorscheme,
title1, title2, label, split=False,
export_dir="static/data"
):
# --- Validaciones básicas ---
if gdf.empty:
raise ValueError("GeoDataFrame is empty")
for col in [name1, name2]:
if col not in gdf.columns:
raise ValueError(f"Column '{col}' not found in gdf")
if gdf.crs is None:
gdf = gdf.set_crs(epsg=32615)
gdf = gdf.to_crs(4326)
os.makedirs(export_dir, exist_ok=True)
before_path = os.path.join(export_dir, "layer_before.geojson")
after_path = os.path.join(export_dir, "layer_after.geojson")
split_path = os.path.join(export_dir, "layer_split.geojson") if split else None
# --- Configurar colormap y normalización ---
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin=vmin, vmax=vmax)
# --- Copias completas para conservar todas las columnas ---
gdf_before = gdf.copy()
gdf_after = gdf.copy()
# --- Agregar columnas derivadas sin eliminar otras ---
gdf_before["value"] = gdf_before[name1]
gdf_after["value"] = gdf_after[name2]
gdf_before["color"] = gdf_before["value"].apply(lambda x: colors.to_hex(cmap(norm(x))))
gdf_after["color"] = gdf_after["value"].apply(lambda x: colors.to_hex(cmap(norm(x))))
# --- Guardar GeoJSON con todos los datos originales ---
gdf_before.to_file(before_path, driver="GeoJSON")
gdf_after.to_file(after_path, driver="GeoJSON")
# --- Lógica del split (si aplica) ---
if split:
split_field = split if isinstance(split, str) else name1
if split_field not in gdf.columns:
raise ValueError(f"Split field '{split_field}' not found in gdf")
gdf_split = gdf.copy()
cond_100 = gdf_split[split_field] > 100
cond_cap = (gdf_split[split_field] > (100 - gdf_split.get("Pct_Construccion", 0))) & ~cond_100
gdf_split.loc[cond_100, "split_type"] = "100"
gdf_split.loc[cond_100, "color"] = "#b22222"
gdf_split.loc[cond_cap, "split_type"] = "capacidad"
gdf_split.loc[cond_cap, "color"] = "#d2691e"
gdf_split = gdf_split.dropna(subset=["split_type"])
gdf_split.to_file(split_path, driver="GeoJSON")
# --- Crear barra de color horizontal ---
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = []
fig_cbar, ax_cbar = plt.subplots(figsize=(6, 1))
fig_cbar.subplots_adjust(bottom=0.5)
cb = plt.colorbar(sm, cax=ax_cbar, orientation='horizontal')
cb.set_label(label)
# --- Guardar imagen de barra de colores ---
colorbar_path = os.path.join(export_dir, "before_after_colorbar_horizontal.png")
fig_cbar.savefig(colorbar_path, dpi=150, bbox_inches="tight", transparent=True)
plt.close(fig_cbar)
# --- URLs fijas ---
before_url = "/data/layer_before.geojson"
after_url = "/data/layer_after.geojson"
split_url = "/data/layer_split.geojson" if split else None
return {
"before_url": before_url,
"after_url": after_url,
"split_url": split_url,
"before_path": before_path,
"after_path": after_path,
"split_path": split_path,
"colorbar_path": colorbar_path
}
def show_one_plot_and_export(
gdf, name, vmin, vmax, colorscheme,
title, label,
export_dir="static/data"
):
# --- Validaciones básicas ---
if gdf.empty:
raise ValueError("GeoDataFrame is empty")
if name not in gdf.columns:
raise ValueError(f"Column '{name}' not found in gdf")
print("sshow one_plot_and_export")
if gdf.crs is None:
gdf = gdf.set_crs(epsg=32615)
gdf = gdf.to_crs(4326)
os.makedirs(export_dir, exist_ok=True)
layer_path = os.path.join(export_dir, "layer_single.geojson")
# --- Colores por feature ---
cmap = plt.colormaps[colorscheme]
norm = colors.Normalize(vmin=vmin, vmax=vmax)
def value_to_hex(val):
rgba = cmap(norm(val))
return colors.to_hex(rgba, keep_alpha=False)
gdf_export = gdf[[name, "geometry"]].rename(columns={name: "value"})
gdf_export["color"] = gdf_export["value"].apply(value_to_hex)
# --- Guardar GeoJSON ---
gdf_export.to_file(layer_path, driver="GeoJSON")
# --- Crear figura principal ---
n_points = len(gdf)
marker_size = 8500 / n_points
marker_size = max(2, min(marker_size, 200))
fig = plt.figure(figsize=(10, 6))
gs = gridspec.GridSpec(1, 2, width_ratios=[1, 0.03], wspace=0.3)
ax, cax = fig.add_subplot(gs[0]), fig.add_subplot(gs[1])
gdf.plot(column=name, cmap=cmap, norm=norm, ax=ax,
marker='s', markersize=marker_size)
ax.set_title(title, pad=-30)
ax.set_axis_off()
sm = cm.ScalarMappable(cmap=cmap, norm=norm)
sm._A = []
cbar = fig.colorbar(sm, cax=cax, orientation='vertical')
cbar.set_label(label)
# ✅ Crear barra de color horizontal separada
fig_cbar, ax_cbar = plt.subplots(figsize=(6, 1))
fig_cbar.subplots_adjust(bottom=0.5)
cb = plt.colorbar(sm, cax=ax_cbar, orientation='horizontal')
cb.set_label(label)
# --- Guardar como imagen PNG ---
colorbar_path = os.path.join(export_dir, "change_colorbar_horizontal.png")
fig_cbar.savefig(colorbar_path, dpi=150, bbox_inches="tight", transparent=True)
plt.close(fig_cbar)
# ✅ URL pública fija
layer_url = "/data/layer_single.geojson"
return {
"fig": fig,
"layer_path": layer_path,
"layer_url": layer_url,
"colorbar_path": colorbar_path,
"gdf_export": gdf_export[["value", "color", "geometry"]]
}