diff --git a/fit_app.py b/fit_app.py index 96de463..8b23835 100644 --- a/fit_app.py +++ b/fit_app.py @@ -423,8 +423,7 @@ def create_map_plot(df): -###################### -# NEUE VERSION: +# ##################### def create_elevation_plot(df, smooth_points=500): # Originale Daten x = df['time'] @@ -433,23 +432,17 @@ def create_elevation_plot(df, smooth_points=500): # Einfache Glättung: nur Y-Werte glätten, X-Werte beibehalten if len(y) >= 4: # Genug Punkte für cubic interpolation y_numeric = y.to_numpy() - # Nur gültige Y-Punkte für Interpolation mask = ~np.isnan(y_numeric) - if np.sum(mask) >= 4: # Genug gültige Punkte # Index-basierte Interpolation für Y-Werte valid_indices = np.where(mask)[0] valid_y = y_numeric[mask] - # Interpolation über die Indizes - f = interp1d(valid_indices, valid_y, kind='cubic', - bounds_error=False, fill_value='extrapolate') - + f = interp1d(valid_indices, valid_y, kind='cubic', bounds_error=False, fill_value='extrapolate') # Neue Y-Werte für alle ursprünglichen X-Positionen all_indices = np.arange(len(y)) y_smooth = f(all_indices) - # Originale X-Werte beibehalten x_smooth = x else: @@ -461,40 +454,96 @@ def create_elevation_plot(df, smooth_points=500): fig = go.Figure() - # Fläche unter der Kurve (mit geglätteten Daten) - fig.add_trace(go.Scatter( - x=x_smooth, y=y_smooth, - mode='lines', - line=dict(color='#1CAF50'), # Fill between color! - fill='tozeroy', - #fillcolor='rgba(226, 241, 248)', - hoverinfo='skip', - showlegend=False - )) + # Separate Behandlung für positive und negative Bereiche + y_array = np.array(y_smooth) + x_array = np.array(x_smooth) - # Hauptlinie (geglättet) + # Positive Bereiche (Anstiege) - Gradient von transparent unten zu grün oben + positive_mask = y_array >= 0 + if np.any(positive_mask): + # Nulllinie für positive Bereiche + fig.add_trace(go.Scatter( + x=x_array, + y=np.zeros_like(y_array), + mode='lines', + line=dict(width=0), + hoverinfo='skip', + showlegend=False + )) + + # Positive Bereiche mit Gradient nach oben + fig.add_trace(go.Scatter( + x=x_array, + y=np.where(y_array >= 0, y_array, 0), # Nur positive Werte + fill='tonexty', # Fill zur vorherigen Trace (Nulllinie) + mode='lines', + line=dict(width=0), + fillgradient=dict( + type="vertical", + colorscale=[ + (0.0, "rgba(17, 17, 17, 0.0)"), # Transparent unten (bei y=0) + (1.0, "rgba(43, 82, 144, 0.8)") # Blau oben (bei maximaler Höhe) Grün: 73, 189, 115 + ] + ), + hoverinfo='skip', + showlegend=False + )) + + # Negative Bereiche (Abstiege) - Gradient von grün unten zu transparent oben + negative_mask = y_array < 0 + if np.any(negative_mask): + # Nulllinie für negative Bereiche + fig.add_trace(go.Scatter( + x=x_array, + y=np.where(y_array < 0, y_array, 0), # Nur negative Werte + mode='lines', + line=dict(width=0), + hoverinfo='skip', + showlegend=False + )) + + # Negative Bereiche mit Gradient nach unten + fig.add_trace(go.Scatter( + x=x_array, + y=np.zeros_like(y_array), + fill='tonexty', # Fill zur vorherigen Trace (negative Werte) + mode='lines', + line=dict(width=0), + fillgradient=dict( + type="vertical", + colorscale=[ + (0.0, "rgba(43, 82, 144, 0.8)"), # Blau unten (bei minimaler Tiefe) + (1.0, "rgba(17, 17, 17, 0.0)") # Transparent oben (bei y=0) + ] + ), + hoverinfo='skip', + showlegend=False + )) + + # Hauptlinie (geglättet) - über allem fig.add_trace(go.Scatter( - x=x_smooth, y=y_smooth, + x=x_smooth, + y=y_smooth, mode='lines', - line=dict(color='#084C20', width=2), # Line color! + line=dict(color='#2b5290', width=2), # Weiße Linie für bessere Sichtbarkeit name='Elevation', showlegend=False )) - # SUPDER IDEE, ABER GEHT NICHT WEGEN NEUEN smoothed POINTS! GEHT NUR BEI X - #fig.update_traces( - # hovertemplate=( - # #"Time: %{customdata[0]}
" + - # "Distance (km): %{customdata[0]:.2f}
" + - # "Elevation: %{customdata[1]}" + - # "Elapsed Time: %{customdata[2]}" - # ), - # customdata=df[['cum_dist_km','elev', 'time']] - # + # Add horizontal reference line at y=0 + fig.add_shape( + type='line', + x0=df['time_loc'].iloc[0], + x1=df['time_loc'].iloc[-1], + y0=0, + y1=0, + line=dict(color='gray', width=1, dash='dash'), + name='Durchschnittstempo' + ) # Layout im Dark Theme fig.update_layout( - title=dict(text='Höhenprofil relativ zum Startwert', font=dict(size=16, color='white')), + title=dict(text='Höhenprofil (relativ zum Ausgangswert: 0m)', font=dict(size=16, color='white')), xaxis_title='Zeit', yaxis_title='Höhe relativ zum Start (m)', template='plotly_dark', @@ -507,6 +556,92 @@ def create_elevation_plot(df, smooth_points=500): return fig +# Alte Version - normaler gradient fill between: +# def create_elevation_plot(df, smooth_points=500): +# # Originale Daten +# x = df['time'] +# y = df['rel_elev'] +# +# # Einfache Glättung: nur Y-Werte glätten, X-Werte beibehalten +# if len(y) >= 4: # Genug Punkte für cubic interpolation +# y_numeric = y.to_numpy() +# +# # Nur gültige Y-Punkte für Interpolation +# mask = ~np.isnan(y_numeric) +# +# if np.sum(mask) >= 4: # Genug gültige Punkte +# # Index-basierte Interpolation für Y-Werte +# valid_indices = np.where(mask)[0] +# valid_y = y_numeric[mask] +# +# # Interpolation über die Indizes +# f = interp1d(valid_indices, valid_y, kind='cubic', +# bounds_error=False, fill_value='extrapolate') +# +# # Neue Y-Werte für alle ursprünglichen X-Positionen +# all_indices = np.arange(len(y)) +# y_smooth = f(all_indices) +# +# # Originale X-Werte beibehalten +# x_smooth = x +# else: +# # Fallback: originale Daten +# x_smooth, y_smooth = x, y +# else: +# # Zu wenige Punkte: originale Daten verwenden +# x_smooth, y_smooth = x, y +# +# fig = go.Figure() +# +# # Fläche unter der Kurve (mit geglätteten Daten) +# fig.add_trace(go.Scatter( +# x=x_smooth, y=y_smooth, +# mode='lines', +# line=dict(color='#1CAF50'), # Fill between color! +# fill='tozeroy', +# #fillcolor='rgba(226, 241, 248)', +# hoverinfo='skip', +# showlegend=False +# )) +# +# # Hauptlinie (geglättet) +# fig.add_trace(go.Scatter( +# x=x_smooth, y=y_smooth, +# mode='lines', +# line=dict(color='#084C20', width=2), # Line color! +# name='Elevation', +# showlegend=False +# )) +# +# # SUPDER IDEE, ABER GEHT NICHT WEGE NEUEN smoothed POINTS! GEHT NUR BEI X +# #fig.update_traces( +# # hovertemplate=( +# # #"Time: %{customdata[0]}
" + +# # "Distance (km): %{customdata[0]:.2f}
" + +# # "Elevation: %{customdata[1]}" + +# # "Elapsed Time: %{customdata[2]}" +# # ), +# # customdata=df[['cum_dist_km','elev', 'time']] +# # +# +# # Layout im Dark Theme +# fig.update_layout( +# title=dict(text='Höhenprofil relativ zum Startwert', font=dict(size=16, color='white')), +# xaxis_title='Zeit', +# yaxis_title='Höhe relativ zum Start (m)', +# template='plotly_dark', +# paper_bgcolor='#1e1e1e', +# plot_bgcolor='#111111', +# font=dict(color='white'), +# margin=dict(l=40, r=40, t=50, b=40), +# height=400 +# ) +# +# return fig +# ##################### + + + def create_deviation_plot(df): #Distanz-Zeit-Diagramm @@ -613,7 +748,7 @@ def create_heart_rate_plot(df): y=df['hr_smooth'][~mask], mode='lines', #name='Geglättete Herzfrequenz', - line=dict(color='#E43D70', width=2), + line=dict(color='#ff2c48', width=2), showlegend=False, hovertemplate=( "Zeit: %{x}
" + @@ -648,7 +783,7 @@ def create_heart_rate_plot(df): # Annotation für Durchschnittswert fig.add_annotation( - x=df['time_loc'].iloc[int(len(df) * 0.8)], # Bei 80% der Zeit + x=df['time_loc'].iloc[int(len(df) * 0.5)], # Bei 50% der Zeit y=mean_hr, text=f"Ø {mean_hr:.0f} bpm", showarrow=True, @@ -664,13 +799,20 @@ def create_heart_rate_plot(df): # Geschätzte maximale Herzfrequenz (Beispiel: 200 bpm) max_hr_estimated = 200 # oder z. B. 220 - alter - # Definiere feste HR-Zonen in BPM + ## Definiere feste HR-Zonen in BPM + #zones = [ + # {"name": "Zone 1", "lower": 0, "upper": 124, "color": "#F4A4A3"}, # Regeneration (Recovery) + # {"name": "Zone 2", "lower": 124, "upper": 154, "color": "#EF7476"}, # Grundlagenausdauer (Endurance) + # {"name": "Zone 3", "lower": 154, "upper": 169, "color": "#EA4748"}, # Tempo (Aerob) + # {"name": "Zone 4", "lower": 169, "upper": 184, "color": "#E02628"}, # Schwelle (Threshold) (Anaerob) + # {"name": "Zone 5", "lower": 184, "upper": max_hr_estimated, "color": "#B71316"}, # Neuromuskulär (Neuromuskulär) + #] zones = [ - {"name": "Zone 1", "lower": 0, "upper": 124, "color": "#F4A4A3"}, # Regeneration (Recovery) - {"name": "Zone 2", "lower": 124, "upper": 154, "color": "#EF7476"}, # Grundlagenausdauer (Endurance) - {"name": "Zone 3", "lower": 154, "upper": 169, "color": "#EA4748"}, # Tempo - {"name": "Zone 4", "lower": 169, "upper": 184, "color": "#E02628"}, # Schwelle (Threshold) - {"name": "Zone 5", "lower": 184, "upper": max_hr_estimated, "color": "#B71316"}, # Anaerob + {"name": "Zone 1", "lower": 0, "upper": 124, "color": "#4A4A4A"}, # Regeneration (Recovery) (#111111 Transparent) + {"name": "Zone 2", "lower": 124, "upper": 154, "color": "#87CEFA"}, # Grundlagenausdauer (Endurance) + {"name": "Zone 3", "lower": 154, "upper": 169, "color": "#90EE90"}, # Tempo (Aerob) + {"name": "Zone 4", "lower": 169, "upper": 184, "color": "#FFDAB9"}, # Schwelle (Threshold) (Anaerob) + {"name": "Zone 5", "lower": 184, "upper": max_hr_estimated, "color": "#FFB6C1"}, # Neuromuskulär (Neuromuskulär) ] # Zeichne Zonen als Hintergrund (horizontale Rechtecke) @@ -698,7 +840,7 @@ def create_heart_rate_plot(df): ), yaxis=dict( title='Herzfrequenz (bpm)', - rangemode='tozero' + range=[80, 200] # Statt rangemode='tozero' ), template='plotly_dark', paper_bgcolor='#1e1e1e', @@ -762,13 +904,14 @@ def create_pace_bars_plot(df): # Step 4: Create Plotly bar chart fig = go.Figure() fig.add_trace(go.Bar( - x=df['km_start'], + x=df['km_start'], # Mittig unter jeder Bar y=df['pace_min_per_km'], width=df['segment_len'], text=[f"{v:.1f} min/km" if pd.notnull(v) else "" for v in df['pace_min_per_km']], #textposition='outside', textposition='inside', marker_color='#125595', + opacity=0.9, # Transparenz name='Pace pro km', offset=0 ))