diff --git a/src/assets/css/style.css b/src/assets/css/style.css index 326523f..9705af4 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -190,6 +190,13 @@ p.version > a { grid-row: 1; } +/* --- Dashboard inputs --- */ + +.Select--multi .Select-value { + color: var(--primary-color); + background-color: rgba(255, 240, 240, 0.4); +} + /* --- Tables (Dash & Custom) --- */ /* Table Menu (Exports etc) */ diff --git a/src/figures.py b/src/figures.py index 215847f..c32f865 100644 --- a/src/figures.py +++ b/src/figures.py @@ -624,6 +624,25 @@ def make_card( return card +def make_donut(lff: pl.LazyFrame, names_col): + title = data_schema[names_col]["title"] + lff = lff.rename({names_col: title}) + lff = lff.select("uid", title) + lff = lff.group_by(title).len("Nombre") + lff = lff.with_columns(pl.col(title).replace(None, pl.lit("?"))) + fig = px.pie( + lff.collect(engine="streaming"), + values="Nombre", + names=title, + hole=0.4, + color_discrete_sequence=px.colors.qualitative.Safe, + ) + fig = fig.update_traces(texttemplate="%{label}
%{percent}") + fig = fig.update_layout(showlegend=False, font=dict(size=14)) + graph = dcc.Graph(figure=fig) + return graph + + def make_column_picker(page: str): table_data = [] table_columns = [ diff --git a/src/pages/statistiques.py b/src/pages/statistiques.py index 4a1f970..9de6a29 100644 --- a/src/pages/statistiques.py +++ b/src/pages/statistiques.py @@ -5,7 +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, make_card +from src.figures import get_geographic_maps, make_card, make_donut from src.utils import ( departements, df, @@ -69,7 +69,7 @@ layout = [ options=get_enum_values_as_dict( "acheteur_categorie" ), - placeholder="Catégorie d'acheteur", + placeholder="Catégorie", ), ), dbc.Row( @@ -81,14 +81,44 @@ layout = [ options=options_departements, ), ), + html.H5("Titulaire"), + dbc.Row( + dcc.Dropdown( + id="dashboard_titulaire_categorie", + placeholder="Catégorie", + options=get_enum_values_as_dict( + "titulaire_categorie" + ), + ), + ), html.H5("Marché"), dbc.Row( dcc.Dropdown( id="dashboard_marche_type", - placeholder="Type de marché", + placeholder="Type", options=get_enum_values_as_dict("type"), ), ), + dbc.Row( + dcc.Dropdown( + id="dashboard_marche_considerationsSociales", + placeholder="Considérations sociales", + options=get_enum_values_as_dict( + "considerationsSociales" + ), + multi=True, + ), + ), + dbc.Row( + dcc.Dropdown( + id="dashboard_marche_considerationsEnvironnementales", + placeholder="Considérations environnementales", + multi=True, + options=get_enum_values_as_dict( + "considerationsEnvironnementales" + ), + ), + ), ], ), dbc.Col( @@ -112,13 +142,19 @@ layout = [ Input("dashboard_year", "value"), Input("dashboard_acheteur_categorie", "value"), Input("dashboard_acheteur_departement_code", "value"), + Input("dashboard_titulaire_categorie", "value"), Input("dashboard_marche_type", "value"), + Input("dashboard_marche_considerationsSociales", "value"), + Input("dashboard_marche_considerationsEnvironnementales", "value"), ) def udpate_dashboard_cards( dashboard_year, dashboard_acheteur_categorie, dashboard_acheteur_departement_code, + dashboard_titulaire_categorie, dashboard_marche_type, + dashboard_marche_considerationsSociales, + dashboard_marche_considerationsEnvironnementales, ): lff: pl.LazyFrame = df.lazy() lff = lff.select( @@ -127,10 +163,14 @@ def udpate_dashboard_cards( cs.starts_with("titulaire"), "dateNotification", "montant", + "considerationsSociales", + "considerationsEnvironnementales", ) # Application des filtres + ## Période + if dashboard_year: lff = lff.filter(pl.col("dateNotification").dt.year() == int(dashboard_year)) else: @@ -138,6 +178,8 @@ def udpate_dashboard_cards( pl.col("dateNotification") > (datetime.now() - timedelta(days=365)) ) + ## Acheteur + if dashboard_acheteur_categorie: lff = lff.filter(pl.col("acheteur_categorie") == dashboard_acheteur_categorie) @@ -148,12 +190,38 @@ def udpate_dashboard_cards( ) ) + ## Titulaire + + if dashboard_titulaire_categorie: + lff = lff.filter(pl.col("titulaire_categorie") == dashboard_titulaire_categorie) + + ## Marché + if dashboard_marche_type: lff = lff.filter(pl.col("type") == dashboard_marche_type) - # Génération des métriques - dff = lff.collect() + if dashboard_marche_considerationsSociales: + lff = lff.filter( + pl.col("considerationsSociales") + .str.split(", ") + .list.set_intersection(dashboard_marche_considerationsSociales) + .list.len() + > 0 + ) + if dashboard_marche_considerationsEnvironnementales: + lff = lff.filter( + pl.col("considerationsEnvironnementales") + .str.split(", ") + .list.set_intersection(dashboard_marche_considerationsEnvironnementales) + .list.len() + > 0 + ) + + # Génération des métriques + dff = lff.collect(engine="streaming") + + # À transformer en fonction nb_acheteurs = dff.select("acheteur_id").n_unique() nb_titulaires = dff.select("titulaire_id", "titulaire_typeIdentifiant").n_unique() @@ -165,7 +233,6 @@ def udpate_dashboard_cards( cards = [] - # À transformer en fonction card_basic_counts = [ html.P(["Nombre de marchés : ", html.Strong(str(format_number(nb_marches)))]), html.P( @@ -179,6 +246,14 @@ def udpate_dashboard_cards( cards.append(make_card(title="Résumé", paragraphs=card_basic_counts)) + donut_acheteur_categorie = make_donut(lff, "acheteur_categorie") + cards.append(make_card(title="Catégorie d'acheteur", fig=donut_acheteur_categorie)) + + donut_titulaire_categorie = make_donut(lff, "titulaire_categorie") + cards.append( + make_card(title="Catégorie d'entreprise", fig=donut_titulaire_categorie) + ) + geographic_maps: list[dbc.Col] = get_geographic_maps(dff) return dbc.Row(children=cards + geographic_maps) diff --git a/src/utils.py b/src/utils.py index b334d01..642bad4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -697,13 +697,13 @@ def get_button_properties(height): def get_enum_values_as_dict(column_name): - for column in data_schema: - if column == column_name: - options = {} - for value in data_schema[column]["enum"]: - options[value] = value - return options - return {"not_found": "not found"} + try: + options = {} + for value in data_schema[column_name]["enum"]: + options[value] = value + return options + except KeyError: + return {"not_found": "not found"} def invert_columns(columns):