MaxBDKT commited on
Commit
5ba19a2
·
verified ·
1 Parent(s): bd6e018

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +41 -66
src/streamlit_app.py CHANGED
@@ -7,53 +7,43 @@ import os
7
  # Configuration de la page
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
- # --- CSS RADICAL POUR SIDEBAR ET CONTRASTE ---
11
  st.markdown("""
12
  <style>
13
- /* Force le fond global en blanc */
14
  .stApp { background-color: #FFFFFF !important; }
15
 
16
- /* --- SIDEBAR --- */
17
- /* Force le fond de la sidebar en gris très clair pour qu'on voie bien les contours */
18
- [data-testid="stSidebar"] {
19
- background-color: #F8F9FB !important;
20
- border-right: 1px solid #000000;
21
  }
22
 
23
- /* Force TOUS les textes et labels de la sidebar en NOIR */
24
- [data-testid="stSidebar"] .stText,
25
- [data-testid="stSidebar"] label,
26
- [data-testid="stSidebar"] p,
27
- [data-testid="stSidebar"] span {
28
- color: #000000 !important;
29
- font-weight: bold !important;
 
 
30
  }
31
 
32
- /* Force le texte à l'intérieur des boites de sélection (Multiselect / Selectbox) */
33
- div[data-baseweb="select"] * {
34
- color: #000000 !important;
35
- }
36
-
37
- /* Change le fond des boites de saisie pour qu'elles ne soient pas noires */
38
- div[data-baseweb="select"] {
39
- background-color: #FFFFFF !important;
40
- border: 1px solid #000000 !important;
41
- }
42
 
43
  /* --- MAIN AREA --- */
44
- /* Texte Noir partout */
45
  * { color: #000000; }
46
-
47
- /* Metrics et boîtes d'analyse */
48
  [data-testid="stMetricValue"] { color: #000000 !important; font-weight: 800 !important; }
49
- [data-testid="stMetricLabel"] { color: #000000 !important; }
50
  [data-testid="column"] {
51
- padding: 10px !important;
52
- border: 2px solid #000000 !important;
53
- border-radius: 8px !important;
54
- background-color: #FFFFFF !important;
55
  }
56
-
57
  .alert-red { color: #B71C1C !important; font-weight: 900; }
58
  .check-green { color: #1B5E20 !important; font-weight: 900; }
59
  </style>
@@ -72,31 +62,30 @@ try:
72
  all_models = df['model name'].unique().tolist()
73
 
74
  with st.sidebar:
75
- st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
76
  st.title("⚙️ SETTINGS")
 
 
77
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
78
- st.markdown("---")
79
 
80
- # Sélection des modèles
81
  selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2])
82
- st.markdown("---")
83
 
84
- # Standard Compliance
85
  st.subheader("📋 Standard Compliance")
86
  norm_type = st.selectbox("Category", ["None", "City/Trekking", "Kids", "MTB", "Racing"])
87
 
88
- # Valeurs de normes
89
  n_dry, n_wet = 0, 0
90
  if norm_type == "City/Trekking": n_dry, n_wet = 340, 220
91
  elif norm_type == "Kids": n_dry, n_wet = 204, 132
92
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
93
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
94
 
 
95
  with st.expander("🔍 Display Options"):
96
  show_loss = st.checkbox("Show Wet Loss Analysis", value=True)
97
  enable_comparison = st.checkbox("Enable Reference Comparison", value=True)
98
- ref_model = st.selectbox("Reference Model (Benchmark)", options=all_models)
99
- condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
100
 
101
  # --- HEADER ---
102
  if x_input < 70: label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
@@ -104,7 +93,7 @@ try:
104
  else: label, color_alert = "🔥 POWERFUL BRAKING", "#ff4b4b"
105
 
106
  st.markdown(f"""
107
- <div style="background-color:{color_alert}; padding:8px; border-radius:8px; text-align:center; border: 3px solid #000000; margin-bottom: 15px;">
108
  <span style="color:#000000; font-weight:900; font-size:16px;">{label} | Effort: {round(float(x_input), 1)} N</span>
109
  </div>
110
  """, unsafe_allow_html=True)
@@ -115,7 +104,6 @@ try:
115
  x_range = np.linspace(40, 200, 150)
116
  colors = ['#0082C3', '#E63312', '#000000', '#00A14B', '#FFD200']
117
 
118
- # On s'assure que la réf est chargée même si non affichée
119
  row_ref = df[df['model name'] == ref_model].iloc[0]
120
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
121
  ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b']
@@ -131,56 +119,43 @@ try:
131
  fig.add_trace(go.Scatter(x=x_range, y=row['dry a']*x_range+row['dry b'], mode='lines', name=f"{row['model name']} (Dry)", line=dict(color=color, width=4)))
132
  if n_dry > 0:
133
  xt = (n_dry - row['dry b']) / row['dry a']
134
- if xt <= 200: fig.add_trace(go.Scatter(x=[xt], y=[n_dry], mode='markers+text', text=[f"{round(xt,1)}N"], textfont=dict(color="black", size=12, weight=700), textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False))
135
 
136
  if condition_view in ["Both", "Wet only"]:
137
  fig.add_trace(go.Scatter(x=x_range, y=row['wet a']*x_range+row['wet b'], mode='lines', name=f"{row['model name']} (Wet)", line=dict(color=color, width=3, dash='dot')))
138
  if n_wet > 0:
139
  xtw = (n_wet - row['wet b']) / row['wet a']
140
- if xtw <= 200: fig.add_trace(go.Scatter(x=[xtw], y=[n_wet], mode='markers+text', text=[f"{round(xtw,1)}N"], textfont=dict(color="black", size=12, weight=700), textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False))
141
 
142
  fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="#000000")
143
 
144
- # Normes
145
  if n_dry > 0 and (condition_view in ["Both", "Dry only"]):
146
  fig.add_hline(y=n_dry, line_width=3, line_color="#000000", annotation_text=f"Norm Dry: {n_dry}N", annotation_font=dict(color="black", size=12, weight=700))
147
  if n_wet > 0 and (condition_view in ["Both", "Wet only"]):
148
  fig.add_hline(y=n_wet, line_width=3, line_dash="dot", line_color="#000000", annotation_text=f"Norm Wet: {n_wet}N", annotation_font=dict(color="black", size=12, weight=700))
149
 
150
  fig.update_layout(
151
- height=480,
152
- xaxis=dict(title="Lever Effort [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, weight=700), gridcolor="#E0E0E0"),
153
- yaxis=dict(title="Performance [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", size=13, weight=700), gridcolor="#E0E0E0"),
154
  font=dict(color="#000000"), plot_bgcolor='#FFFFFF', paper_bgcolor='#FFFFFF', hovermode="x unified",
155
- legend=dict(font=dict(color="#000000", size=11, weight=700), bordercolor="#000000", borderwidth=2, bgcolor="#FFFFFF")
156
  )
157
  st.plotly_chart(fig, use_container_width=True)
158
 
159
- # --- ANALYSIS DASHBOARD ---
160
- st.markdown(f"<p style='color:#000000; font-weight:900; font-size:16px;'>📊 Analysis | Ref: {ref_model}</p>", unsafe_allow_html=True)
161
 
162
  if not filtered_df.empty:
163
  cols = st.columns(len(comparison_results))
164
  for i, res in enumerate(comparison_results):
165
  with cols[i]:
166
- st.markdown(f"<p style='font-size:14px; font-weight:900; color:#000000; margin-bottom:5px; text-decoration: underline;'>{res['name']} {'⭐' if (res['name'] == ref_model) else ''}</p>", unsafe_allow_html=True)
167
-
168
  if condition_view in ["Both", "Dry only"]:
169
  dv = round(res['dry'], 1)
170
- st.metric("Dry Perf.", f"{dv} N", f"{dv - round(ref_dry_val, 1):+.1f} N Vs Ref." if enable_comparison and not (res['name'] == ref_model) else None)
171
- if n_dry > 0:
172
- xt = (n_dry - res['row']['dry b']) / res['row']['dry a']
173
- if xt > 180: st.markdown(f"<div class='alert-red'>❌ NON CONFORME SEC<br>Target: {round(xt,1)}N > 180N</div>", unsafe_allow_html=True)
174
- else: st.markdown(f"<div class='check-green'>✅ Conforme Sec ({round(xt,1)}N)</div>", unsafe_allow_html=True)
175
-
176
  if condition_view in ["Both", "Wet only"]:
177
  wv = round(res['wet'], 1)
178
- st.metric("Wet Perf.", f"{wv} N", f"{wv - round(ref_wet_val, 1):+.1f} N Vs Ref." if enable_comparison and not (res['name'] == ref_model) else None)
179
- if n_wet > 0:
180
- xtw = (n_wet - res['row']['wet b']) / res['row']['wet a']
181
- if xtw > 180: st.markdown(f"<div class='alert-red'>❌ NON CONFORME HUMIDE<br>Target: {round(xtw,1)}N > 180N</div>", unsafe_allow_html=True)
182
- else: st.markdown(f"<div class='check-green'>✅ Conforme Humide ({round(xtw,1)}N)</div>", unsafe_allow_html=True)
183
-
184
  if show_loss and condition_view == "Both":
185
  loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0
186
  st.metric("Wet Loss", f"-{round(loss_pct, 1)}%", delta_color="inverse")
 
7
  # Configuration de la page
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
+ # --- CSS RADICAL : COMPACTAGE SIDEBAR + CONTRASTE ---
11
  st.markdown("""
12
  <style>
 
13
  .stApp { background-color: #FFFFFF !important; }
14
 
15
+ /* --- COMPACTAGE SIDEBAR --- */
16
+ /* On réduit l'espace entre tous les blocs de la sidebar */
17
+ [data-testid="stSidebar"] [data-testid="stVerticalBlock"] {
18
+ gap: 0.5rem !important;
19
+ padding-top: 0rem !important;
20
  }
21
 
22
+ /* On réduit la marge au dessus des titres et sous les lignes --- */
23
+ hr { margin: 0.5rem 0 !important; }
24
+ .stSlider { margin-bottom: -15px !important; }
25
+ .stMultiSelect { margin-bottom: -10px !important; }
26
+ .stSelectbox { margin-bottom: -10px !important; }
27
+
28
+ [data-testid="stSidebar"] { background-color: #F8F9FB !important; border-right: 1px solid #000000; }
29
+ [data-testid="stSidebar"] label, [data-testid="stSidebar"] p, [data-testid="stSidebar"] span {
30
+ color: #000000 !important; font-weight: bold !important;
31
  }
32
 
33
+ /* --- GESTION DES LISTES DÉROULANTES (NOIR SUR BLANC) --- */
34
+ div[data-baseweb="select"] > div { background-color: #000000 !important; color: #FFFFFF !important; }
35
+ div[data-baseweb="select"] span { color: #FFFFFF !important; }
36
+ ul[role="listbox"] { background-color: #000000 !important; }
37
+ ul[role="listbox"] li { color: #FFFFFF !important; background-color: #000000 !important; }
38
+ ul[role="listbox"] li:hover { background-color: #333333 !important; }
 
 
 
 
39
 
40
  /* --- MAIN AREA --- */
 
41
  * { color: #000000; }
 
 
42
  [data-testid="stMetricValue"] { color: #000000 !important; font-weight: 800 !important; }
 
43
  [data-testid="column"] {
44
+ padding: 10px !important; border: 2px solid #000000 !important;
45
+ border-radius: 8px !important; background-color: #FFFFFF !important;
 
 
46
  }
 
47
  .alert-red { color: #B71C1C !important; font-weight: 900; }
48
  .check-green { color: #1B5E20 !important; font-weight: 900; }
49
  </style>
 
62
  all_models = df['model name'].unique().tolist()
63
 
64
  with st.sidebar:
65
+ st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=180)
66
  st.title("⚙️ SETTINGS")
67
+
68
+ # Les blocs sont maintenant rapprochés par le CSS "gap" et "margin"
69
  x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
 
70
 
 
71
  selected_models = st.multiselect("Select Models to Display", options=all_models, default=all_models[:2])
 
72
 
 
73
  st.subheader("📋 Standard Compliance")
74
  norm_type = st.selectbox("Category", ["None", "City/Trekking", "Kids", "MTB", "Racing"])
75
 
76
+ # Valeurs de normes officielles
77
  n_dry, n_wet = 0, 0
78
  if norm_type == "City/Trekking": n_dry, n_wet = 340, 220
79
  elif norm_type == "Kids": n_dry, n_wet = 204, 132
80
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
81
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
82
 
83
+ st.markdown("---")
84
  with st.expander("🔍 Display Options"):
85
  show_loss = st.checkbox("Show Wet Loss Analysis", value=True)
86
  enable_comparison = st.checkbox("Enable Reference Comparison", value=True)
87
+ ref_model = st.selectbox("Reference Model", options=all_models)
88
+ condition_view = st.radio("Conditions", ["Both", "Dry only", "Wet only"], index=0)
89
 
90
  # --- HEADER ---
91
  if x_input < 70: label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
 
93
  else: label, color_alert = "🔥 POWERFUL BRAKING", "#ff4b4b"
94
 
95
  st.markdown(f"""
96
+ <div style="background-color:{color_alert}; padding:8px; border-radius:8px; text-align:center; border: 3px solid #000000; margin-bottom: 10px;">
97
  <span style="color:#000000; font-weight:900; font-size:16px;">{label} | Effort: {round(float(x_input), 1)} N</span>
98
  </div>
99
  """, unsafe_allow_html=True)
 
104
  x_range = np.linspace(40, 200, 150)
105
  colors = ['#0082C3', '#E63312', '#000000', '#00A14B', '#FFD200']
106
 
 
107
  row_ref = df[df['model name'] == ref_model].iloc[0]
108
  ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
109
  ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b']
 
119
  fig.add_trace(go.Scatter(x=x_range, y=row['dry a']*x_range+row['dry b'], mode='lines', name=f"{row['model name']} (Dry)", line=dict(color=color, width=4)))
120
  if n_dry > 0:
121
  xt = (n_dry - row['dry b']) / row['dry a']
122
+ if xt <= 200: fig.add_trace(go.Scatter(x=[xt], y=[n_dry], mode='markers+text', text=[f"{round(xt,1)}N"], textfont=dict(color="black", size=11, weight=700), textposition="top center", marker=dict(color=color, size=10, symbol='x'), showlegend=False))
123
 
124
  if condition_view in ["Both", "Wet only"]:
125
  fig.add_trace(go.Scatter(x=x_range, y=row['wet a']*x_range+row['wet b'], mode='lines', name=f"{row['model name']} (Wet)", line=dict(color=color, width=3, dash='dot')))
126
  if n_wet > 0:
127
  xtw = (n_wet - row['wet b']) / row['wet a']
128
+ if xtw <= 200: fig.add_trace(go.Scatter(x=[xtw], y=[n_wet], mode='markers+text', text=[f"{round(xtw,1)}N"], textfont=dict(color="black", size=11, weight=700), textposition="bottom center", marker=dict(color=color, size=10, symbol='circle-open'), showlegend=False))
129
 
130
  fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="#000000")
131
 
 
132
  if n_dry > 0 and (condition_view in ["Both", "Dry only"]):
133
  fig.add_hline(y=n_dry, line_width=3, line_color="#000000", annotation_text=f"Norm Dry: {n_dry}N", annotation_font=dict(color="black", size=12, weight=700))
134
  if n_wet > 0 and (condition_view in ["Both", "Wet only"]):
135
  fig.add_hline(y=n_wet, line_width=3, line_dash="dot", line_color="#000000", annotation_text=f"Norm Wet: {n_wet}N", annotation_font=dict(color="black", size=12, weight=700))
136
 
137
  fig.update_layout(
138
+ height=450, xaxis=dict(title="Lever Effort [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", weight=700), gridcolor="#E0E0E0"),
139
+ yaxis=dict(title="Performance [N]", color="#000000", linecolor="#000000", linewidth=3, tickfont=dict(color="#000000", weight=700), gridcolor="#E0E0E0"),
 
140
  font=dict(color="#000000"), plot_bgcolor='#FFFFFF', paper_bgcolor='#FFFFFF', hovermode="x unified",
141
+ legend=dict(font=dict(color="#000000", size=11, weight=700), bordercolor="#000000", borderwidth=1, bgcolor="#FFFFFF")
142
  )
143
  st.plotly_chart(fig, use_container_width=True)
144
 
145
+ # --- ANALYSIS ---
146
+ st.markdown(f"<p style='color:#000000; font-weight:900; font-size:16px; margin-top:-10px;'>📊 Analysis | Ref: {ref_model}</p>", unsafe_allow_html=True)
147
 
148
  if not filtered_df.empty:
149
  cols = st.columns(len(comparison_results))
150
  for i, res in enumerate(comparison_results):
151
  with cols[i]:
152
+ st.markdown(f"<p style='font-size:13px; font-weight:900; color:#000000; margin-bottom:0px; text-decoration: underline;'>{res['name']} {'⭐' if (res['name'] == ref_model) else ''}</p>", unsafe_allow_html=True)
 
153
  if condition_view in ["Both", "Dry only"]:
154
  dv = round(res['dry'], 1)
155
+ st.metric("Dry Perf.", f"{dv} N", f"{dv - round(ref_dry_val, 1):+.1f} N" if enable_comparison and not (res['name'] == ref_model) else None)
 
 
 
 
 
156
  if condition_view in ["Both", "Wet only"]:
157
  wv = round(res['wet'], 1)
158
+ st.metric("Wet Perf.", f"{wv} N", f"{wv - round(ref_wet_val, 1):+.1f} N" if enable_comparison and not (res['name'] == ref_model) else None)
 
 
 
 
 
159
  if show_loss and condition_view == "Both":
160
  loss_pct = ((res['dry'] - res['wet']) / res['dry'] * 100) if res['dry'] != 0 else 0
161
  st.metric("Wet Loss", f"-{round(loss_pct, 1)}%", delta_color="inverse")