feat(etapes): graphique données par étape et par seuil
This commit is contained in:
+137
-1
@@ -17,6 +17,141 @@ register_page(
|
|||||||
image_url=META_CONTENT["image_url"],
|
image_url=META_CONTENT["image_url"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lane(*bars):
|
||||||
|
"""Une ligne d'étape : fond segmenté en 5 + barres positionnées."""
|
||||||
|
return html.Div(
|
||||||
|
className="etapes-lane",
|
||||||
|
children=[
|
||||||
|
html.Div(
|
||||||
|
className="etapes-segs",
|
||||||
|
children=[html.Div() for _ in range(5)],
|
||||||
|
),
|
||||||
|
*bars,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _bar(label, color, style):
|
||||||
|
base = {"backgroundColor": color}
|
||||||
|
base.update(style)
|
||||||
|
return html.Div(label, className="etapes-bar", style=base)
|
||||||
|
|
||||||
|
|
||||||
|
def build_chart():
|
||||||
|
return html.Div(
|
||||||
|
className="etapes-chart-scroll",
|
||||||
|
children=html.Div(
|
||||||
|
className="etapes-chart",
|
||||||
|
children=[
|
||||||
|
# En-tête : coin vide + 5 marqueurs de seuils
|
||||||
|
html.Div(className="etapes-corner"),
|
||||||
|
html.Div(
|
||||||
|
className="etapes-xhead",
|
||||||
|
children=[
|
||||||
|
html.Div("0 €", className="etapes-xcell"),
|
||||||
|
html.Div(
|
||||||
|
[html.Strong("40 000 €"), "seuil DECP"],
|
||||||
|
className="etapes-xcell",
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
[html.Strong("90 000 €"), "publicité"],
|
||||||
|
className="etapes-xcell",
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
[html.Strong("140 k€ / 216 k€"), "seuils formalisés (UE)"],
|
||||||
|
className="etapes-xcell",
|
||||||
|
),
|
||||||
|
html.Div(
|
||||||
|
[html.Strong("5,404 M€"), "travaux (UE)"],
|
||||||
|
className="etapes-xcell",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
# Programmation
|
||||||
|
html.Div("Programmation", className="etapes-stage"),
|
||||||
|
_lane(
|
||||||
|
_bar(
|
||||||
|
"Approch — sourcing / préinformation (non réglementaire)",
|
||||||
|
"#7c5cff",
|
||||||
|
{"left": "2%", "right": "2%"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# Publicité (appel d'offres)
|
||||||
|
html.Div(
|
||||||
|
["Publicité ", html.Small("(appel d'offres)")],
|
||||||
|
className="etapes-stage",
|
||||||
|
),
|
||||||
|
_lane(
|
||||||
|
_bar(
|
||||||
|
"Journaux d'annonces légales",
|
||||||
|
"#f79009",
|
||||||
|
{"left": "40%", "right": "40%", "top": "6px", "height": "20px"},
|
||||||
|
),
|
||||||
|
_bar(
|
||||||
|
"BOAMP",
|
||||||
|
"#1570ef",
|
||||||
|
{"left": "40%", "right": "2%", "top": "28px", "height": "20px"},
|
||||||
|
),
|
||||||
|
_bar(
|
||||||
|
"JOUE — avis de marché",
|
||||||
|
"#0e9384",
|
||||||
|
{"left": "60%", "right": "2%", "top": "6px", "height": "20px"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# Attribution
|
||||||
|
html.Div("Attribution", className="etapes-stage"),
|
||||||
|
_lane(
|
||||||
|
_bar(
|
||||||
|
"DECP — données essentielles",
|
||||||
|
"#12b76a",
|
||||||
|
{"left": "20%", "right": "2%", "top": "6px", "height": "20px"},
|
||||||
|
),
|
||||||
|
_bar(
|
||||||
|
"JOUE — avis d'attribution",
|
||||||
|
"#0e9384",
|
||||||
|
{"left": "60%", "right": "2%", "top": "28px", "height": "20px"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# Contrat (vide)
|
||||||
|
html.Div("Contrat", className="etapes-stage"),
|
||||||
|
html.Div(
|
||||||
|
"— aucune donnée publiée aujourd'hui —",
|
||||||
|
className="etapes-lane etapes-empty",
|
||||||
|
),
|
||||||
|
# Paiement (vide)
|
||||||
|
html.Div("Paiement", className="etapes-stage"),
|
||||||
|
html.Div(
|
||||||
|
"— aucune donnée publiée aujourd'hui —",
|
||||||
|
className="etapes-lane etapes-empty",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_legend():
|
||||||
|
items = [
|
||||||
|
("Approch", "#7c5cff"),
|
||||||
|
("Journaux d'annonces légales", "#f79009"),
|
||||||
|
("BOAMP", "#1570ef"),
|
||||||
|
("JOUE", "#0e9384"),
|
||||||
|
("DECP", "#12b76a"),
|
||||||
|
]
|
||||||
|
return html.Div(
|
||||||
|
className="etapes-legend",
|
||||||
|
children=[
|
||||||
|
html.Span(
|
||||||
|
[
|
||||||
|
html.I(style={"backgroundColor": color}),
|
||||||
|
label,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for label, color in items
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
layout = html.Div(
|
layout = html.Div(
|
||||||
className="container",
|
className="container",
|
||||||
children=[
|
children=[
|
||||||
@@ -28,7 +163,8 @@ layout = html.Div(
|
|||||||
"principales publications de données par **étape** (de haut en "
|
"principales publications de données par **étape** (de haut en "
|
||||||
"bas) et par **seuil** (de gauche à droite, en euros hors taxes)."
|
"bas) et par **seuil** (de gauche à droite, en euros hors taxes)."
|
||||||
),
|
),
|
||||||
# Le graphique sera inséré ici en Task 2
|
build_chart(),
|
||||||
|
build_legend(),
|
||||||
dcc.Markdown(
|
dcc.Markdown(
|
||||||
"**À noter :** l'axe horizontal n'est pas linéaire — les seuils "
|
"**À noter :** l'axe horizontal n'est pas linéaire — les seuils "
|
||||||
"sont espacés régulièrement pour rester lisibles. Les étapes "
|
"sont espacés régulièrement pour rester lisibles. Les étapes "
|
||||||
|
|||||||
Reference in New Issue
Block a user