diff --git a/fit_app.py b/fit_app.py index bc1f84c..476a0ea 100644 --- a/fit_app.py +++ b/fit_app.py @@ -14,7 +14,7 @@ import datetime from math import radians, sin, cos, sqrt, asin import dash -from dash import dcc, html, Input, Output, Dash +from dash import dcc, html, Input, Output, Dash, State import dash_bootstrap_components as dbc import pandas as pd @@ -340,7 +340,7 @@ def create_info_banner(df): 'justifyContent': 'space-around', 'backgroundColor': '#1e1e1e', 'color': 'white', - 'padding': '20px', + 'padding': '5px', 'marginBottom': '5px', 'borderRadius': '10px', 'width': '100%', @@ -355,25 +355,6 @@ def create_info_banner(df): # START OF THE PLOTS # ============================================================================= def create_map_plot(df): - # fig = px.line_map( - # df, - # lat='lat', - # lon='lon', - # hover_name='time', - # hover_data={ - # 'cum_dist_km': ':.2f', - # 'duration_hms': True, - # 'lat': False, - # 'lon': False, - # 'time': False - # }, - # labels={ - # 'cum_dist_km': 'Distance (km) ', - # 'duration_hms': 'Elapsed Time ' - # }, - # zoom=13, - # height=800 - # ) fig = px.line_map( df, lat='lat', @@ -394,9 +375,9 @@ def create_map_plot(df): customdata=df[['cum_dist_km', 'speed_kmh', 'heart_rate', 'duration_hms']] ) # Define map style and the line ontop - fig.update_layout(map_style="open-street-map") - # The built-in plotly.js styles are: carto-darkmatter, carto-positron, open-street-map, stamen-terrain, stamen-toner, stamen-watercolor, white-bg - # The built-in Mapbox styles are: basic, streets, outdoors, light, dark, satellite, satellite-streets + fig.update_layout(map_style="open-street-map") #My-Fav: open-street-map, satellite-streets, dark, white-bg + # Possible Options: + # 'basic', 'carto-darkmatter', 'carto-darkmatter-nolabels', 'carto-positron', 'carto-positron-nolabels', 'carto-voyager', 'carto-voyager-nolabels', 'dark', 'light', 'open-street-map', 'outdoors', 'satellite', 'satellite-streets', 'streets', 'white-bg'. fig.update_traces(line=dict(color="#f54269", width=3)) # Start / Stop marker @@ -410,17 +391,33 @@ def create_map_plot(df): lat=[end['lat']], lon=[end['lon']], mode='markers+text', marker=dict(size=12, color='#b9fc62'), text=['Stop'], name='Stop', textposition='bottom left' )) - fig.update_layout(paper_bgcolor='#1e1e1e', font=dict(color='white')) + # THIS IS MY ELEVATION-PLOT SHOW POSITION-MARKER IN MAP-PLOT: + fig.add_trace(go.Scattermap( + lat=[], + lon=[], + mode="markers", + marker=dict(size=18, color="#42B1E5", symbol="circle"), + name="Hovered Point" + )) + # KOMPAKTE LAYOUT-EINSTELLUNGEN fig.update_layout( - legend=dict( - orientation='h', # horizontal layout - yanchor='top', - y=-0.01, # move legend below the map - xanchor='center', - x=0.5, - font=dict(color='white') + paper_bgcolor='#1e1e1e', + font=dict(color='white'), + # Margins reduzieren für kompakteren Plot + margin=dict(l=60, r=45, t=10, b=50), # Links, Rechts, Oben, Unten + # Plotly-Toolbar konfigurieren + showlegend=True, + # Kompakte Legend + legend=dict( + orientation='h', # horizontal layout + yanchor='top', + y=-0.02, # move legend below the map + xanchor='center', + x=0.5, + font=dict(color='white', size=10) # Kleinere Schrift + ) ) -) + return fig @@ -881,18 +878,20 @@ app.layout = html.Div([ ) def load_fit_data(path): df = process_fit(path) + return df.to_json(date_format='iso', orient='split') -# Callback 2: Update All Plots +# Callback 2: Update All (static) Plots @app.callback( Output('info-banner', 'children'), - Output('fig-map', 'figure'), + Output('fig-map', 'figure', allow_duplicate=True), Output('fig-elevation', 'figure'), Output('fig_deviation', 'figure'), Output('fig_speed', 'figure'), Output('fig_hr', 'figure'), Output('fig_pace_bars', 'figure'), - Input('stored-df', 'data') + Input('stored-df', 'data'), + prevent_initial_call=True ) def update_all_plots(json_data): df = pd.read_json(io.StringIO(json_data), orient='split') @@ -908,6 +907,27 @@ def update_all_plots(json_data): return info, fig_map, fig_elev, fig_dev, fig_speed, fig_hr, fig_pace +# Callback 3: Hover → update only hover (dynamic) marker +@app.callback( + Output('fig-map', 'figure'), + Input('fig-elevation', 'hoverData'), + State('fig-map', 'figure'), + State('stored-df', 'data'), + prevent_initial_call=True +) +def highlight_map(hoverData, fig_map, json_data): + df = pd.read_json(io.StringIO(json_data), orient='split') + + if hoverData is not None: + point_index = hoverData['points'][0]['pointIndex'] + lat, lon = df.iloc[point_index][['lat', 'lon']] + + # update the last trace (the empty Hovered Point trace) + fig_map['data'][-1]['lat'] = [lat] + fig_map['data'][-1]['lon'] = [lon] + + return fig_map + # === Run Server === if __name__ == '__main__': app.run(debug=True, port=8051)