From ab3377ef60ef39804188a4ff56b96c86d2a4ba42 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Tue, 17 Mar 2026 17:16:24 +0100 Subject: [PATCH] Grid de cards avec points de rupture #65 --- data/departements.json | 8 ++++++ src/figures.py | 57 +++++++++++++++++++++++---------------- src/pages/statistiques.py | 37 +++++++++---------------- 3 files changed, 55 insertions(+), 47 deletions(-) diff --git a/data/departements.json b/data/departements.json index 4115895..1f346d0 100644 --- a/data/departements.json +++ b/data/departements.json @@ -391,8 +391,16 @@ "departement": "La Réunion", "region": "La Réunion" }, + "975": { + "departement": "Saint-Pierre-et-Miquelon", + "region": "Saint-Pierre-et-Miquelon" + }, "976": { "departement": "Mayotte", "region": "Mayotte" + }, + "977": { + "departement": "Saint-Barthelemy", + "region": "Saint-Barthelemy" } } diff --git a/src/figures.py b/src/figures.py index 1d3a132..556e802 100644 --- a/src/figures.py +++ b/src/figures.py @@ -417,18 +417,12 @@ def get_geographic_maps(dff: pl.DataFrame) -> list | None: }, } - # Liste des codes départements Outre-Mer - dom_codes = [code for code in regions.keys() if code != "Métropole"] - print("dom codes", dom_codes) - def make_map_data(region_code: str) -> tuple[list, str or None]: lff: pl.LazyFrame = dff.lazy() if region_code == "Métropole": lff = lff.filter( - ( - ~pl.col("acheteur_departement_code").is_in(dom_codes) - | (~pl.col("titulaire_departement_code").is_in(dom_codes)) - ) + (pl.col("acheteur_departement_code").str.len_chars() == 2) + & (pl.col("titulaire_departement_code").str.len_chars() == 2) ) else: lff = lff.filter( @@ -436,14 +430,14 @@ def get_geographic_maps(dff: pl.DataFrame) -> list | None: | (pl.col("titulaire_departement_code") == code) ) - nb_marches = lff.select("uid").group_by("uid").first().collect().height + nb_marches = lff.select("uid").collect()["uid"].n_unique() if nb_marches == 0: return [], None dfs = [] - if (code == "Métropole" and nb_marches > 20000) or ( + if (code == "Métropole" and nb_marches > 30000) or ( code != "Métropole" and nb_marches > 10000 ): _map_type: str = "chloropleth" @@ -505,10 +499,9 @@ def get_geographic_maps(dff: pl.DataFrame) -> list | None: for code in regions.keys(): regions[code]["data"], map_type = make_map_data(code) - print("region", regions[code]["name"], len(regions[code]["data"][0]), map_type) if map_type == "chloropleth": - map_graph = make_chloropleth_map(regions[code]) # call chloropleth function + map_graph = make_chloropleth_map(regions[code]) elif map_type == "clusters": map_graph = make_clusters_map(regions[code]) elif map_type is None: @@ -516,15 +509,9 @@ def get_geographic_maps(dff: pl.DataFrame) -> list | None: else: raise ValueError(f"Map type '{map_type}' not recognised") - col = dbc.Col( - children=[ - html.H5(regions[code]["name"]), - map_graph, - ], - md=6 if code == "Métropole" else 3, - width=12, - className="mb-4", - ) + md = 6 if code == "Métropole" else 3 + + col = make_card(regions[code]["name"], md=md, fig=map_graph) cols.append(col) return cols @@ -543,7 +530,6 @@ def make_chloropleth_map(region: dict) -> dcc.Graph: range_color=(df_map["uid"].min(), df_map["uid"].max()), labels={"uid": "Marchés attribués"}, scope="europe", - width=400, height=400, ) @@ -561,7 +547,7 @@ def make_chloropleth_map(region: dict) -> dcc.Graph: return graph -def make_clusters_map(region: dict): +def make_clusters_map(region: dict) -> dl.Map: # JavaScript functions for styling ns = Namespace("dash_clientside", "leaflet") point_to_layer = ns("pointToLayer") @@ -615,6 +601,31 @@ def make_clusters_map(region: dict): return leaflet_map +def make_card( + title: str, subtitle=None, fig=None, paragraphs=None, md=3, width=12 +) -> dbc.Col: + children = [] + if title: + children.append(html.H5(title, className="card-title")) + if subtitle: + children.append(html.H6(subtitle, className="card-subtitle mb-2 text-muted")) + if fig: + children.append(fig) + if paragraphs: + for p in paragraphs: + p.className = "card-text" + children.append(p) + + card = dbc.Col( + html.Div(html.Div(className="card-body", children=children), className="card"), + lg=6, + xl=4, + # width=width, + # className="mb-4", + ) + return card + + def make_column_picker(page: str): table_data = [] table_columns = [ diff --git a/src/pages/statistiques.py b/src/pages/statistiques.py index 99fcbc7..df71edd 100644 --- a/src/pages/statistiques.py +++ b/src/pages/statistiques.py @@ -5,9 +5,7 @@ import polars as pl import polars.selectors as cs from dash import Input, Output, callback, dcc, html, register_page -from src.figures import ( - get_geographic_maps, -) +from src.figures import get_geographic_maps, make_card from src.utils import ( departements, df, @@ -52,8 +50,8 @@ layout = [ dbc.Row( [ dbc.Col( - width=12, - md=3, + xl=3, + lg=4, id="filters", children=[ html.H5("Période d'attribution"), @@ -87,22 +85,10 @@ layout = [ ), dbc.Col( width=12, - md=9, + lg=8, + xl=9, id="cards", - children=[ - dbc.Row( - ( - dbc.Col( - width=6, - md=4, - className="card", - id="card_basic_counts", - ) - ), - className="mb-4", - ), - dbc.Row(id="maps_row"), - ], + children=[], ), ] ) @@ -114,8 +100,7 @@ layout = [ @callback( - Output("card_basic_counts", "children"), - Output("maps_row", "children"), + Output("cards", "children"), Input("dashboard_year", "value"), Input("dashboard_acheteur_categorie", "value"), Input("dashboard_acheteur_departement_code", "value"), @@ -163,6 +148,8 @@ def udpate_dashboard_cards( total_montant = df_per_uid.select(pl.col("montant").sum()).item() nb_marches = df_per_uid.height + cards = [] + # À transformer en fonction card_basic_counts = [ html.P(["Nombre de marchés : ", html.Strong(str(format_number(nb_marches)))]), @@ -175,6 +162,8 @@ def udpate_dashboard_cards( html.P(["Montant total : ", html.Strong(format_number(total_montant) + " €")]), ] - geographic_maps = get_geographic_maps(dff) + cards.append(make_card(title="Résumé", paragraphs=card_basic_counts)) - return card_basic_counts, geographic_maps + geographic_maps: list[dbc.Col] = get_geographic_maps(dff) + + return dbc.Row(children=cards + geographic_maps)