File size: 5,779 Bytes
0aee67c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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