elev_plot display style update - gradient fill, heart_rate bmp Zones new display style
This commit is contained in:
229
fit_app.py
229
fit_app.py
@@ -423,8 +423,7 @@ def create_map_plot(df):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
######################
|
# #####################
|
||||||
# NEUE VERSION:
|
|
||||||
def create_elevation_plot(df, smooth_points=500):
|
def create_elevation_plot(df, smooth_points=500):
|
||||||
# Originale Daten
|
# Originale Daten
|
||||||
x = df['time']
|
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
|
# Einfache Glättung: nur Y-Werte glätten, X-Werte beibehalten
|
||||||
if len(y) >= 4: # Genug Punkte für cubic interpolation
|
if len(y) >= 4: # Genug Punkte für cubic interpolation
|
||||||
y_numeric = y.to_numpy()
|
y_numeric = y.to_numpy()
|
||||||
|
|
||||||
# Nur gültige Y-Punkte für Interpolation
|
# Nur gültige Y-Punkte für Interpolation
|
||||||
mask = ~np.isnan(y_numeric)
|
mask = ~np.isnan(y_numeric)
|
||||||
|
|
||||||
if np.sum(mask) >= 4: # Genug gültige Punkte
|
if np.sum(mask) >= 4: # Genug gültige Punkte
|
||||||
# Index-basierte Interpolation für Y-Werte
|
# Index-basierte Interpolation für Y-Werte
|
||||||
valid_indices = np.where(mask)[0]
|
valid_indices = np.where(mask)[0]
|
||||||
valid_y = y_numeric[mask]
|
valid_y = y_numeric[mask]
|
||||||
|
|
||||||
# Interpolation über die Indizes
|
# Interpolation über die Indizes
|
||||||
f = interp1d(valid_indices, valid_y, kind='cubic',
|
f = interp1d(valid_indices, valid_y, kind='cubic', bounds_error=False, fill_value='extrapolate')
|
||||||
bounds_error=False, fill_value='extrapolate')
|
|
||||||
|
|
||||||
# Neue Y-Werte für alle ursprünglichen X-Positionen
|
# Neue Y-Werte für alle ursprünglichen X-Positionen
|
||||||
all_indices = np.arange(len(y))
|
all_indices = np.arange(len(y))
|
||||||
y_smooth = f(all_indices)
|
y_smooth = f(all_indices)
|
||||||
|
|
||||||
# Originale X-Werte beibehalten
|
# Originale X-Werte beibehalten
|
||||||
x_smooth = x
|
x_smooth = x
|
||||||
else:
|
else:
|
||||||
@@ -461,40 +454,96 @@ def create_elevation_plot(df, smooth_points=500):
|
|||||||
|
|
||||||
fig = go.Figure()
|
fig = go.Figure()
|
||||||
|
|
||||||
# Fläche unter der Kurve (mit geglätteten Daten)
|
# Separate Behandlung für positive und negative Bereiche
|
||||||
fig.add_trace(go.Scatter(
|
y_array = np.array(y_smooth)
|
||||||
x=x_smooth, y=y_smooth,
|
x_array = np.array(x_smooth)
|
||||||
mode='lines',
|
|
||||||
line=dict(color='#1CAF50'), # Fill between color!
|
|
||||||
fill='tozeroy',
|
|
||||||
#fillcolor='rgba(226, 241, 248)',
|
|
||||||
hoverinfo='skip',
|
|
||||||
showlegend=False
|
|
||||||
))
|
|
||||||
|
|
||||||
# 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(
|
fig.add_trace(go.Scatter(
|
||||||
x=x_smooth, y=y_smooth,
|
x=x_smooth,
|
||||||
|
y=y_smooth,
|
||||||
mode='lines',
|
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',
|
name='Elevation',
|
||||||
showlegend=False
|
showlegend=False
|
||||||
))
|
))
|
||||||
|
|
||||||
# SUPDER IDEE, ABER GEHT NICHT WEGEN NEUEN smoothed POINTS! GEHT NUR BEI X
|
# Add horizontal reference line at y=0
|
||||||
#fig.update_traces(
|
fig.add_shape(
|
||||||
# hovertemplate=(
|
type='line',
|
||||||
# #"Time: %{customdata[0]}<br>" +
|
x0=df['time_loc'].iloc[0],
|
||||||
# "Distance (km): %{customdata[0]:.2f}<br>" +
|
x1=df['time_loc'].iloc[-1],
|
||||||
# "Elevation: %{customdata[1]}<extra></extra>" +
|
y0=0,
|
||||||
# "Elapsed Time: %{customdata[2]}<extra></extra>"
|
y1=0,
|
||||||
# ),
|
line=dict(color='gray', width=1, dash='dash'),
|
||||||
# customdata=df[['cum_dist_km','elev', 'time']]
|
name='Durchschnittstempo'
|
||||||
#
|
)
|
||||||
|
|
||||||
# Layout im Dark Theme
|
# Layout im Dark Theme
|
||||||
fig.update_layout(
|
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',
|
xaxis_title='Zeit',
|
||||||
yaxis_title='Höhe relativ zum Start (m)',
|
yaxis_title='Höhe relativ zum Start (m)',
|
||||||
template='plotly_dark',
|
template='plotly_dark',
|
||||||
@@ -507,6 +556,92 @@ def create_elevation_plot(df, smooth_points=500):
|
|||||||
|
|
||||||
return fig
|
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
|
def create_deviation_plot(df): #Distanz-Zeit-Diagramm
|
||||||
@@ -613,7 +748,7 @@ def create_heart_rate_plot(df):
|
|||||||
y=df['hr_smooth'][~mask],
|
y=df['hr_smooth'][~mask],
|
||||||
mode='lines',
|
mode='lines',
|
||||||
#name='Geglättete Herzfrequenz',
|
#name='Geglättete Herzfrequenz',
|
||||||
line=dict(color='#E43D70', width=2),
|
line=dict(color='#ff2c48', width=2),
|
||||||
showlegend=False,
|
showlegend=False,
|
||||||
hovertemplate=(
|
hovertemplate=(
|
||||||
"Zeit: %{x}<br>" +
|
"Zeit: %{x}<br>" +
|
||||||
@@ -648,7 +783,7 @@ def create_heart_rate_plot(df):
|
|||||||
|
|
||||||
# Annotation für Durchschnittswert
|
# Annotation für Durchschnittswert
|
||||||
fig.add_annotation(
|
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,
|
y=mean_hr,
|
||||||
text=f"Ø {mean_hr:.0f} bpm",
|
text=f"Ø {mean_hr:.0f} bpm",
|
||||||
showarrow=True,
|
showarrow=True,
|
||||||
@@ -664,13 +799,20 @@ def create_heart_rate_plot(df):
|
|||||||
# Geschätzte maximale Herzfrequenz (Beispiel: 200 bpm)
|
# Geschätzte maximale Herzfrequenz (Beispiel: 200 bpm)
|
||||||
max_hr_estimated = 200 # oder z. B. 220 - alter
|
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 = [
|
zones = [
|
||||||
{"name": "Zone 1", "lower": 0, "upper": 124, "color": "#F4A4A3"}, # Regeneration (Recovery)
|
{"name": "Zone 1", "lower": 0, "upper": 124, "color": "#4A4A4A"}, # Regeneration (Recovery) (#111111 Transparent)
|
||||||
{"name": "Zone 2", "lower": 124, "upper": 154, "color": "#EF7476"}, # Grundlagenausdauer (Endurance)
|
{"name": "Zone 2", "lower": 124, "upper": 154, "color": "#87CEFA"}, # Grundlagenausdauer (Endurance)
|
||||||
{"name": "Zone 3", "lower": 154, "upper": 169, "color": "#EA4748"}, # Tempo
|
{"name": "Zone 3", "lower": 154, "upper": 169, "color": "#90EE90"}, # Tempo (Aerob)
|
||||||
{"name": "Zone 4", "lower": 169, "upper": 184, "color": "#E02628"}, # Schwelle (Threshold)
|
{"name": "Zone 4", "lower": 169, "upper": 184, "color": "#FFDAB9"}, # Schwelle (Threshold) (Anaerob)
|
||||||
{"name": "Zone 5", "lower": 184, "upper": max_hr_estimated, "color": "#B71316"}, # Anaerob
|
{"name": "Zone 5", "lower": 184, "upper": max_hr_estimated, "color": "#FFB6C1"}, # Neuromuskulär (Neuromuskulär)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Zeichne Zonen als Hintergrund (horizontale Rechtecke)
|
# Zeichne Zonen als Hintergrund (horizontale Rechtecke)
|
||||||
@@ -698,7 +840,7 @@ def create_heart_rate_plot(df):
|
|||||||
),
|
),
|
||||||
yaxis=dict(
|
yaxis=dict(
|
||||||
title='Herzfrequenz (bpm)',
|
title='Herzfrequenz (bpm)',
|
||||||
rangemode='tozero'
|
range=[80, 200] # Statt rangemode='tozero'
|
||||||
),
|
),
|
||||||
template='plotly_dark',
|
template='plotly_dark',
|
||||||
paper_bgcolor='#1e1e1e',
|
paper_bgcolor='#1e1e1e',
|
||||||
@@ -762,13 +904,14 @@ def create_pace_bars_plot(df):
|
|||||||
# Step 4: Create Plotly bar chart
|
# Step 4: Create Plotly bar chart
|
||||||
fig = go.Figure()
|
fig = go.Figure()
|
||||||
fig.add_trace(go.Bar(
|
fig.add_trace(go.Bar(
|
||||||
x=df['km_start'],
|
x=df['km_start'], # Mittig unter jeder Bar
|
||||||
y=df['pace_min_per_km'],
|
y=df['pace_min_per_km'],
|
||||||
width=df['segment_len'],
|
width=df['segment_len'],
|
||||||
text=[f"{v:.1f} min/km" if pd.notnull(v) else "" for v in df['pace_min_per_km']],
|
text=[f"{v:.1f} min/km" if pd.notnull(v) else "" for v in df['pace_min_per_km']],
|
||||||
#textposition='outside',
|
#textposition='outside',
|
||||||
textposition='inside',
|
textposition='inside',
|
||||||
marker_color='#125595',
|
marker_color='#125595',
|
||||||
|
opacity=0.9, # Transparenz
|
||||||
name='Pace pro km',
|
name='Pace pro km',
|
||||||
offset=0
|
offset=0
|
||||||
))
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user