import pandas as pd import plotly.express as px import streamlit as st import geopandas as gpd # --- Configuration and Constants --- MISSING_VALUE_PLACEHOLDER = -1 # --- Helper for Color Scale --- def _generate_custom_colorscale(min_val, max_val, red_gradient=False): if red_gradient: reversed_rdylgn_colors = px.colors.sequential.Reds return reversed_rdylgn_colors else: custom_colorscale = [[0.0, "lightgrey"]] reversed_rdylgn_colors = px.colors.diverging.RdYlGn[::-1] normalized_min_actual = (min_val - MISSING_VALUE_PLACEHOLDER) / ( max_val - MISSING_VALUE_PLACEHOLDER ) normalized_max_actual = (max_val - MISSING_VALUE_PLACEHOLDER) / ( max_val - MISSING_VALUE_PLACEHOLDER ) num_steps = len(reversed_rdylgn_colors) for i, color in enumerate(reversed_rdylgn_colors): normalized_point = normalized_min_actual + ( normalized_max_actual - normalized_min_actual ) * (i / (num_steps - 1)) if normalized_point > 0.0: custom_colorscale.append([normalized_point, color]) return sorted(custom_colorscale, key=lambda x: x[0]) def display_choropleth_map_country( df: pd.DataFrame, geojson_data: dict, metric_name: str = "prixm2moyen", metric_description: str = "Average Price per Square Meter", title: str = "Average Price per Square Meter in France (2015-2024)", height: int = 1000, width: int = 1400, red_gradient: bool = False, ): df_agg = ( df .groupby(["code_departement", "annee"])[metric_name] .mean() .reset_index() ) metric_min = df_agg[metric_name].min() metric_max = df_agg[metric_name].max() custom_colorscale = _generate_custom_colorscale(metric_min, metric_max, red_gradient) fig = px.choropleth_map( df_agg, geojson=geojson_data, locations="code_departement", featureidkey="properties.code", color=metric_name, labels={metric_name: metric_description}, range_color=[metric_min, metric_max], color_continuous_scale=custom_colorscale, center={"lat": 46.603354, "lon": 1.888334}, zoom=5, opacity=0.75, hover_name="code_departement", hover_data={metric_name: ":.1f", "annee": True}, title=title, height=height, width=width, animation_frame="annee", animation_group="code_departement", ) fig.update_traces(marker_line_width=0) if fig.layout.updatemenus: try: fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1500 fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = 500 except IndexError: st.warning("Could not set animation speed.") else: st.warning("No animation updatemenus found.") return fig def display_choropleth_map_for_department( df: pd.DataFrame, department_code: str, geojson_data: dict, min_global_prixm2moyen: float, max_global_prixm2moyen: float, metric_name: str = "prixm2moyen", metric_description: str = "Average Price per Square Meter", title: str = "Average Price per Square Meter", height_graph: int = 1000, width_graph: int = 1400, red_gradient: bool = False, ): df_filtered = df[df["code_departement"] == department_code].copy() filtered_features = [ feature for feature in geojson_data["features"] if feature["properties"]["code"].startswith(department_code) ] filtered_geojson = {"type": "FeatureCollection", "features": filtered_features} custom_colorscale = _generate_custom_colorscale( min_global_prixm2moyen, max_global_prixm2moyen, red_gradient ) center_lat, center_lon, zoom_level = 46.603354, 1.888334, 6 if filtered_features: department_gdf = gpd.GeoDataFrame.from_features(filtered_geojson["features"]) if not department_gdf.empty: dissolved_department = department_gdf.dissolve() department_centroid = dissolved_department.geometry.centroid.iloc[0] center_lat, center_lon = department_centroid.y, department_centroid.x minx, miny, maxx, maxy = dissolved_department.total_bounds width, height = maxx - minx, maxy - miny if width < 0.2 and height < 0.2: zoom_level = 10 elif width < 0.5 and height < 0.5: zoom_level = 9 else: zoom_level = 8 fig = px.choropleth_map( df_filtered, geojson=filtered_geojson, locations="code_commune_insee", featureidkey="properties.code", color=metric_name, labels={metric_name: metric_description}, range_color=[min_global_prixm2moyen, max_global_prixm2moyen], # colorscale in red gradient color_continuous_scale=custom_colorscale, center={"lat": center_lat, "lon": center_lon}, zoom=zoom_level, opacity=0.75, hover_name="code_commune_insee", hover_data={metric_name: ":.0f", "annee": True}, title=title, height=height_graph, width=width_graph, animation_frame="annee", animation_group="code_commune_insee", ) fig.update_traces(marker_line_width=0) if fig.layout.updatemenus: try: fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 750 fig.layout.updatemenus[0].buttons[0].args[1]["transition"]["duration"] = 250 except IndexError: st.warning("Could not set animation speed.") else: st.warning("No animation updatemenus found.") return fig