MaxBDKT commited on
Commit
03b6841
·
verified ·
1 Parent(s): 7d75a7b

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +95 -91
src/streamlit_app.py CHANGED
@@ -7,6 +7,28 @@ import os
7
  # Page Configuration
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  @st.cache_data
11
  def load_data():
12
  current_dir = os.path.dirname(__file__)
@@ -17,23 +39,23 @@ def load_data():
17
 
18
  try:
19
  df = load_data()
 
20
 
21
  # --- SIDEBAR ---
22
  with st.sidebar:
23
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
24
  st.title("⚙️ Settings")
25
- x_input = st.slider("🫱 Lever Effort (N)", 40, 200, 100)
26
-
 
 
 
 
 
27
  st.markdown("---")
28
- st.subheader("🔍 Display Options")
29
- show_loss = st.checkbox("Show Efficiency Loss (Wet vs Dry)", value=True)
30
- enable_comparison = st.checkbox("Enable Comparison Mode (Model vs Model)", value=False)
31
-
32
- all_models = df['model name'].unique().tolist()
33
  selected_models = st.multiselect("Select Models", options=all_models, default=all_models[:2])
34
- condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
35
 
36
- # --- DIAGNOSTIC HEADER ---
37
  if x_input < 70:
38
  label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
39
  elif 70 <= x_input <= 110:
@@ -42,93 +64,75 @@ try:
42
  label, color_alert = "🔥 POWERFUL BRAKING", "#ff4b4b"
43
 
44
  st.markdown(f"""
45
- <div style="background-color:{color_alert}; padding:15px; border-radius:10px; text-align:center; border: 1px solid #ddd;">
46
- <h2 style="color:black; margin:0;">{label}</h2>
47
- <p style="color:black; font-weight:bold; margin:5px 0 0 0;">Lever Effort: {round(float(x_input), 1)} N</p>
48
  </div>
49
  """, unsafe_allow_html=True)
50
 
51
- # --- MAIN AREA ---
52
- col1, col2 = st.columns([3, 1])
53
-
54
- with col1:
55
- filtered_df = df[df['model name'].isin(selected_models)]
56
- fig = go.Figure()
57
- x_range = np.linspace(40, 200, 100)
58
- colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
59
-
60
- comparison_results = []
61
-
62
- for i, (index, row) in enumerate(filtered_df.iterrows()):
63
- color = colors[i % len(colors)]
64
- y_dry_val = row['dry a'] * x_input + row['dry b']
65
- y_wet_val = row['wet a'] * x_input + row['wet b']
66
-
67
- comparison_results.append({"name": row['model name'], "dry": y_dry_val, "wet": y_wet_val})
68
-
69
- y_plot_dry = row['dry a'] * x_range + row['dry b']
70
- y_plot_wet = row['wet a'] * x_range + row['wet b']
71
-
72
- # Custom Hover Template: %{x} is removed from the body and put in the title
73
- hover_dry = "<b>" + row['model name'] + " (Dry)</b><br>Perf: %{y:.1f}<extra></extra>"
74
- hover_wet = "<b>" + row['model name'] + " (Wet)</b><br>Perf: %{y:.1f}<extra></extra>"
75
-
76
- if condition_view in ["Both", "Dry only"]:
77
- fig.add_trace(go.Scatter(x=x_range, y=y_plot_dry, mode='lines',
78
- name=f"{row['model name']} (Dry)",
79
- line=dict(color=color, width=4),
80
- hovertemplate=hover_dry))
81
- if condition_view in ["Both", "Wet only"]:
82
- fig.add_trace(go.Scatter(x=x_range, y=y_plot_wet, mode='lines',
83
- name=f"{row['model name']} (Wet)",
84
- line=dict(color=color, width=2, dash='dot'),
85
- hovertemplate=hover_wet))
86
-
87
- fig.add_vline(x=x_input, line_width=3, line_dash="dash", line_color="black")
88
-
89
- fig.update_layout(
90
- xaxis_title="Lever Effort (N)",
91
- yaxis_title="Performance",
92
- plot_bgcolor='white',
93
- hovermode="x unified", # Combine labels at the same X
94
- hoverlabel=dict(bgcolor="white", font_size=12)
95
- )
96
-
97
- # This part ensures the X value appears only once at the top of the unified hoverbox
98
- fig.update_xaxes(showspikes=True, spikecolor="gray", spikesnap="cursor", spikemode="across")
99
 
100
- st.plotly_chart(fig, use_container_width=True)
101
-
102
- with col2:
103
- st.subheader("💡 Analysis")
104
- if not filtered_df.empty:
105
- max_dry = max([r['dry'] for r in comparison_results])
106
- max_wet = max([r['wet'] for r in comparison_results])
107
-
108
- for res in comparison_results:
109
- st.write(f"### {res['name']}")
110
- dry_val = round(res['dry'], 1)
111
- if enable_comparison and len(selected_models) > 1:
112
- diff_dry = round(res['dry'] - max_dry, 1)
113
- suffix = " (Best)" if diff_dry == 0 else f" ({diff_dry} vs Best)"
114
- st.write(f"**Dry:** {dry_val}{suffix}")
115
- else:
116
- st.write(f"**Dry:** {dry_val}")
117
-
118
- wet_val = round(res['wet'], 1)
119
- if enable_comparison and len(selected_models) > 1:
120
- diff_wet = round(res['wet'] - max_wet, 1)
121
- suffix = " (Best)" if diff_wet == 0 else f" ({diff_wet} vs Best)"
122
- st.write(f"**Wet:** {wet_val}{suffix}")
123
- else:
124
- st.write(f"**Wet:** {wet_val}")
125
-
126
- if show_loss:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  loss = ((res['dry'] - res['wet']) / res['dry']) * 100 if res['dry'] != 0 else 0
128
- st.warning(f"Efficiency Loss: -{round(loss, 1)}%")
129
- st.markdown("---")
130
- else:
131
- st.info("Select models.")
132
 
133
  except Exception as e:
134
  st.error(f"Error: {e}")
 
7
  # Page Configuration
8
  st.set_page_config(page_title="Brake Performance Lab", layout="wide", page_icon="🚲")
9
 
10
+ # Style CSS pour réduire la police et compacter l'affichage
11
+ st.markdown("""
12
+ <style>
13
+ .small-font {
14
+ font-size:12px !important;
15
+ color: black !important;
16
+ }
17
+ [data-testid="stMetricValue"] {
18
+ font-size: 20px !important;
19
+ color: black !important;
20
+ }
21
+ [data-testid="stMetricDelta"] {
22
+ font-size: 13px !important;
23
+ }
24
+ [data-testid="column"] {
25
+ padding: 5px !important;
26
+ border: 1px solid #f0f2f6;
27
+ border-radius: 5px;
28
+ }
29
+ </style>
30
+ """, unsafe_allow_html=True)
31
+
32
  @st.cache_data
33
  def load_data():
34
  current_dir = os.path.dirname(__file__)
 
39
 
40
  try:
41
  df = load_data()
42
+ all_models = df['model name'].unique().tolist()
43
 
44
  # --- SIDEBAR ---
45
  with st.sidebar:
46
  st.image("https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Decathlon_Logo.svg/1280px-Decathlon_Logo.svg.png", width=200)
47
  st.title("⚙️ Settings")
48
+ x_input = st.slider("🫱 Lever Effort [N]", 40, 200, 100)
49
+ st.markdown("---")
50
+ with st.expander("🔍 Display Options"):
51
+ show_loss = st.checkbox("Show Wet Loss (%)", value=True)
52
+ enable_comparison = st.checkbox("Enable Ref. Comparison", value=False)
53
+ ref_model = st.selectbox("Reference Model", options=all_models)
54
+ condition_view = st.radio("Conditions to display", ["Both", "Dry only", "Wet only"], index=0)
55
  st.markdown("---")
 
 
 
 
 
56
  selected_models = st.multiselect("Select Models", options=all_models, default=all_models[:2])
 
57
 
58
+ # --- DIAGNOSTIC HEADER (COMPACT) ---
59
  if x_input < 70:
60
  label, color_alert = "❄️ LIGHT BRAKING", "#a1c4fd"
61
  elif 70 <= x_input <= 110:
 
64
  label, color_alert = "🔥 POWERFUL BRAKING", "#ff4b4b"
65
 
66
  st.markdown(f"""
67
+ <div style="background-color:{color_alert}; padding:5px; border-radius:8px; text-align:center; border: 1.5px solid #333; margin-bottom: 10px;">
68
+ <span style="color:black; font-weight:bold; font-size:14px;">{label} | Effort: {round(float(x_input), 1)} N</span>
 
69
  </div>
70
  """, unsafe_allow_html=True)
71
 
72
+ # --- GRAPHIC AREA ---
73
+ filtered_df = df[df['model name'].isin(selected_models)]
74
+ fig = go.Figure()
75
+ x_range = np.linspace(40, 200, 100)
76
+ colors = ['#0082C3', '#E63312', '#333333', '#00A14B', '#FFD200']
77
+
78
+ # Reference data
79
+ row_ref = df[df['model name'] == ref_model].iloc[0]
80
+ ref_dry_val = row_ref['dry a'] * x_input + row_ref['dry b']
81
+ ref_wet_val = row_ref['wet a'] * x_input + row_ref['wet b']
82
+
83
+ comparison_results = []
84
+ for i, (index, row) in enumerate(filtered_df.iterrows()):
85
+ color = colors[i % len(colors)]
86
+ y_dry_val = row['dry a'] * x_input + row['dry b']
87
+ y_wet_val = row['wet a'] * x_input + row['wet b']
88
+ comparison_results.append({"name": row['model name'], "dry": y_dry_val, "wet": y_wet_val})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
+ y_plot_dry = row['dry a'] * x_range + row['dry b']
91
+ y_plot_wet = row['wet a'] * x_range + row['wet b']
92
+
93
+ if condition_view in ["Both", "Dry only"]:
94
+ fig.add_trace(go.Scatter(x=x_range, y=y_plot_dry, mode='lines', name=f"{row['model name']} (Dry)", line=dict(color=color, width=4), hovertemplate=f"<b>{row['model name']}</b><br>Perf: %{{y:.1f}} N<extra></extra>"))
95
+ if condition_view in ["Both", "Wet only"]:
96
+ fig.add_trace(go.Scatter(x=x_range, y=y_plot_wet, mode='lines', name=f"{row['model name']} (Wet)", line=dict(color=color, width=2, dash='dot'), hovertemplate=f"<b>{row['model name']}</b><br>Perf: %{{y:.1f}} N<extra></extra>"))
97
+
98
+ fig.add_vline(x=x_input, line_width=2, line_dash="dash", line_color="black")
99
+ fig.update_layout(height=450, xaxis_title="Lever Effort [N]", yaxis_title="Performance [N]", font=dict(color="black"), plot_bgcolor='white', paper_bgcolor='white', hovermode="x unified", legend=dict(font=dict(size=10, color="black"), bordercolor="black", borderwidth=1))
100
+ fig.update_xaxes(showline=True, linewidth=2, linecolor='black', gridcolor='#EEEEEE')
101
+ fig.update_yaxes(showline=True, linewidth=2, linecolor='black', gridcolor='#EEEEEE')
102
+
103
+ st.plotly_chart(fig, use_container_width=True)
104
+
105
+ # --- ANALYSIS DASHBOARD (BOTTOM) ---
106
+ st.markdown(f"<p class='small-font'><b>📊 Dashboard Analysis [N]</b> | Reference: {ref_model}</p>", unsafe_allow_html=True)
107
+
108
+ if not filtered_df.empty:
109
+ cols = st.columns(len(comparison_results))
110
+ for i, res in enumerate(comparison_results):
111
+ with cols[i]:
112
+ is_ref = (res['name'] == ref_model)
113
+ st.markdown(f"<p style='font-size:13px; font-weight:bold; margin-bottom:0;'>{res['name']} {'⭐' if is_ref else ''}</p>", unsafe_allow_html=True)
114
+
115
+ if condition_view in ["Both", "Dry only"]:
116
+ d_val = round(res['dry'], 1)
117
+ if enable_comparison and not is_ref:
118
+ diff = d_val - round(ref_dry_val, 1)
119
+ pct = (diff / ref_dry_val * 100) if ref_dry_val != 0 else 0
120
+ st.metric("Dry Performance", f"{d_val} N", f"{diff:+.1f} N ({pct:+.1f}%)")
121
+ else:
122
+ st.metric("Dry Performance", f"{d_val} N")
123
+
124
+ if condition_view in ["Both", "Wet only"]:
125
+ w_val = round(res['wet'], 1)
126
+ if enable_comparison and not is_ref:
127
+ diff_w = w_val - round(ref_wet_val, 1)
128
+ pct_w = (diff_w / ref_wet_val * 100) if ref_wet_val != 0 else 0
129
+ st.metric("Wet Performance", f"{w_val} N", f"{diff_w:+.1f} N ({pct_w:+.1f}%)")
130
+ else:
131
+ st.metric("Wet Performance", f"{w_val} N")
132
+
133
+ if show_loss and condition_view == "Both":
134
  loss = ((res['dry'] - res['wet']) / res['dry']) * 100 if res['dry'] != 0 else 0
135
+ st.markdown(f"<p style='color:red; font-size:11px; margin-top:-10px;'>Wet Loss: -{round(loss, 1)}%</p>", unsafe_allow_html=True)
 
 
 
136
 
137
  except Exception as e:
138
  st.error(f"Error: {e}")