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

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +72 -104
src/streamlit_app.py CHANGED
@@ -4,48 +4,47 @@ import numpy as np
4
  import plotly.graph_objects as go
5
  import os
6
 
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>
50
  """, unsafe_allow_html=True)
51
 
@@ -62,103 +61,72 @@ try:
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"
92
- elif 70 <= x_input <= 110: label, color_alert = "⚖️ MODERATE BRAKING", "#ffdb58"
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)
100
-
101
- # --- GRAPHIC AREA ---
102
  filtered_df = df[df['model name'].isin(selected_models)]
103
  fig = go.Figure()
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']
110
 
111
- comparison_results = []
112
- for i, (index, row) in enumerate(filtered_df.iterrows()):
113
- color = colors[i % len(colors)]
114
- y_d = row['dry a'] * x_input + row['dry b']
115
- y_w = row['wet a'] * x_input + row['wet b']
116
- comparison_results.append({"name": row['model name'], "dry": y_d, "wet": y_w, "row": row})
117
-
118
  if condition_view in ["Both", "Dry only"]:
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")
162
-
163
  except Exception as e:
164
  st.error(f"Error: {e}")
 
4
  import plotly.graph_objects as go
5
  import os
6
 
7
+ # Configuration
8
+ st.set_page_config(page_title="Brake Performance Lab", layout="wide")
9
 
10
+ # --- CSS DE LA DERNIÈRE CHANCE (FORÇAGE NOIR SUR BLANC) ---
11
  st.markdown("""
12
  <style>
13
+ /* 1. Fond blanc pur partout */
14
+ .stApp, [data-testid="stSidebar"] { background-color: #FFFFFF !important; }
 
 
 
 
 
 
15
 
16
+ /* 2. Compactage Sidebar */
17
+ [data-testid="stSidebar"] [data-testid="stVerticalBlock"] { gap: 0.1rem !important; padding-top: 0rem !important; }
 
 
 
18
 
19
+ /* 3. Force le NOIR sur TOUS les textes (Labels, Titres, Menus) */
20
+ * { color: #000000 !important; font-family: sans-serif; }
21
+
22
+ /* 4. Fix spécifique pour les LISTES DÉROULANTES (Selectbox / Multiselect) */
23
+ /* On force le fond en blanc et la bordure en noir */
24
+ div[data-baseweb="select"] {
25
+ border: 2px solid #000000 !important;
26
+ background-color: #FFFFFF !important;
27
+ }
28
+ /* On force le texte des options dans la liste qui s'ouvre */
29
+ ul[role="listbox"] { background-color: #FFFFFF !important; border: 2px solid #000000 !important; }
30
+ li[role="option"] { background-color: #FFFFFF !important; color: #000000 !important; }
31
+ li[role="option"]:hover { background-color: #0082C3 !important; color: #FFFFFF !important; }
32
+
33
+ /* Fix pour les tags (modèles sélectionnés) : Fond bleu, texte blanc pour qu'ils ressortent */
34
+ [data-testid="stMultiSelect"] span {
35
+ background-color: #0082C3 !important;
36
+ color: #FFFFFF !important;
37
  }
38
 
39
+ /* 5. Metrics et boîtes d'analyse en bas */
 
 
 
 
 
 
 
 
 
40
  [data-testid="column"] {
41
+ padding: 8px !important; border: 2px solid #000000 !important;
42
  border-radius: 8px !important; background-color: #FFFFFF !important;
43
  }
44
+ [data-testid="stMetricValue"] { font-weight: 800 !important; font-size: 20px !important; }
45
+
46
+ /* On cache les indicateurs de flèches qui peuvent être gris */
47
+ [data-testid="stMetricDelta"] svg { display: none; }
48
  </style>
49
  """, unsafe_allow_html=True)
50
 
 
61
  all_models = df['model name'].unique().tolist()
62
 
63
  with st.sidebar:
64
+ st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=150)
65
+ st.markdown("**SETTINGS**")
66
+ x_input = st.slider("Lever Effort [N]", 40, 200, 100)
67
+ selected_models = st.multiselect("Models", options=all_models, default=all_models[:2])
68
+ norm_type = st.selectbox("Norm Category", ["None", "City/Trekking", "Kids", "MTB", "Racing"])
 
 
 
 
 
69
 
 
70
  n_dry, n_wet = 0, 0
71
  if norm_type == "City/Trekking": n_dry, n_wet = 340, 220
72
  elif norm_type == "Kids": n_dry, n_wet = 204, 132
73
  elif norm_type == "MTB": n_dry, n_wet = 425, 280
74
  elif norm_type == "Racing": n_dry, n_wet = 425, 260
75
 
76
+ with st.expander("Options"):
77
+ show_loss = st.checkbox("Show Loss", value=True)
78
+ enable_comparison = st.checkbox("Enable Ref", value=True)
79
+ ref_model = st.selectbox("Ref Model", options=all_models)
80
+ condition_view = st.radio("View", ["Both", "Dry only", "Wet only"])
 
81
 
82
+ # --- DIAGNOSTIC ---
83
+ label, color = ("LIGHT", "#a1c4fd") if x_input < 70 else (("MODERATE", "#ffdb58") if x_input <= 110 else ("POWERFUL", "#ff4b4b"))
84
+ st.markdown(f"<div style='background-color:{color}; padding:5px; border:2px solid #000; text-align:center; font-weight:bold;'>{label} BRAKING | {x_input} N</div>", unsafe_allow_html=True)
 
85
 
86
+ # --- GRAPHIC (FIX NOIR TOTAL) ---
 
 
 
 
 
 
87
  filtered_df = df[df['model name'].isin(selected_models)]
88
  fig = go.Figure()
89
+ x_range = np.linspace(40, 200, 100)
90
+ colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
91
 
92
  row_ref = df[df['model name'] == ref_model].iloc[0]
93
+ ref_d, ref_w = row_ref['dry a']*x_input+row_ref['dry b'], row_ref['wet a']*x_input+row_ref['wet b']
 
94
 
95
+ for i, (idx, row) in enumerate(filtered_df.iterrows()):
96
+ c = colors[i % len(colors)]
 
 
 
 
 
97
  if condition_view in ["Both", "Dry only"]:
98
+ fig.add_trace(go.Scatter(x=x_range, y=row['dry a']*x_range+row['dry b'], name=f"{row['model name']} (D)", line=dict(color=c, width=4)))
 
 
 
 
99
  if condition_view in ["Both", "Wet only"]:
100
+ fig.add_trace(go.Scatter(x=x_range, y=row['wet a']*x_range+row['wet b'], name=f"{row['model name']} (W)", line=dict(color=c, width=2, dash='dot')))
 
 
 
 
 
 
 
 
 
 
101
 
102
+ # Fix pour les noms des AXES (on force le NOIR pur ici)
103
  fig.update_layout(
104
+ height=450, plot_bgcolor='white', paper_bgcolor='white',
105
+ xaxis=dict(title=dict(text="Lever Effort [N]", font=dict(color="black", size=14, family="Arial Black")),
106
+ tickfont=dict(color="black", size=12, weight=700), linecolor="black", linewidth=2, gridcolor="#EEE"),
107
+ yaxis=dict(title=dict(text="Performance [N]", font=dict(color="black", size=14, family="Arial Black")),
108
+ tickfont=dict(color="black", size=12, weight=700), linecolor="black", linewidth=2, gridcolor="#EEE"),
109
+ legend=dict(font=dict(color="black", weight=700), bordercolor="black", borderwidth=1),
110
+ hovermode="x unified"
111
  )
112
  st.plotly_chart(fig, use_container_width=True)
113
 
114
  # --- ANALYSIS ---
115
+ st.markdown("**ANALYSIS**")
 
116
  if not filtered_df.empty:
117
+ cols = st.columns(len(filtered_df))
118
+ for i, (idx, row) in enumerate(filtered_df.iterrows()):
119
  with cols[i]:
120
+ st.markdown(f"**{row['model name']}**")
121
+ d_val = round(row['dry a']*x_input + row['dry b'], 1)
122
+ w_val = round(row['wet a']*x_input + row['wet b'], 1)
123
+
124
  if condition_view in ["Both", "Dry only"]:
125
+ st.metric("Dry", f"{d_val}N", f"{d_val-round(ref_d,1):+.1f}N Vs Ref" if enable_comparison and row['model name']!=ref_model else None)
 
126
  if condition_view in ["Both", "Wet only"]:
127
+ st.metric("Wet", f"{w_val}N", f"{w_val-round(ref_w,1):+.1f}N Vs Ref" if enable_comparison and row['model name']!=ref_model else None)
128
+ if show_loss and condition_view=="Both":
129
+ loss = ((d_val - w_val) / d_val * 100) if d_val != 0 else 0
130
+ st.metric("Loss", f"-{round(loss,1)}%", f"{round(w_val-d_val,1)}N vs Dry", delta_color="inverse")
 
 
131
  except Exception as e:
132
  st.error(f"Error: {e}")