elev_plot display style update - gradient fill, heart_rate bmp Zones new display style

This commit is contained in:
2025-09-18 01:04:34 +02:00
parent ed6c8ab94a
commit 121f93004d

View File

@@ -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]}<br>" +
# "Distance (km): %{customdata[0]:.2f}<br>" +
# "Elevation: %{customdata[1]}<extra></extra>" +
# "Elapsed Time: %{customdata[2]}<extra></extra>"
# ),
# 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]}<br>" +
# # "Distance (km): %{customdata[0]:.2f}<br>" +
# # "Elevation: %{customdata[1]}<extra></extra>" +
# # "Elapsed Time: %{customdata[2]}<extra></extra>"
# # ),
# # 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}<br>" +
@@ -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
))