# -*- coding: utf-8 -*-
"""
Created on Sat Apr 5 07:29:18 2025
@author: win11
"""
from dash import Dash, dcc,html, callback, Input, Output,State
import pandas as pd
import plotly.graph_objects as go
import dash_bootstrap_components as dbc
# import json
# import numpy as np
# import requests
# Download CSV sheet at: https://drive.google.com/file/d/1NyBhnTVVwWBblfeA_bifGR9DHuYpaGXu/view?usp=sharing
# Source animated chart
data_finals = pd.read_csv("data_finals_complete.csv")
dfi = pd.read_csv('cup-info.csv')
# #Insert the missing "The" to the cupname to easily find a match for the cup in dfi
dfi['cup'] = dfi['cup'].apply(lambda x: 'The '+x)
# # stylesheet with the .dbc class to style dcc, DataTable and AG Grid components with a Bootstrap theme
dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
# # if using the vizro theme
vizro_bootstrap = "https://cdn.jsdelivr.net/gh/mckinsey/vizro@main/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css?v=2"
# #min and max for range of y-axis linechart
# #the input csv has been adjusted with the correct time for 2019 Grand Cup Challenge
rangemin_finish_time = data_finals['finish_time'].min() -20
rangemax_finish_time = data_finals['finish_time'].max() +20
select_boatclass = dcc.Dropdown( id='select_boatclass', options=[
{'label': i, 'value': i} for i in data_finals['boatclass'].unique()
],
value='M8',
clearable=False,
multi = True,
persistence = 'session',
placeholder='Filter by boatclass...')
select_weather_cond = dcc.Dropdown( id='select_weather', options=[
{'label':'Temperature (C)', 'value':'temperature_C'},
{'label':'Wind (km/h)', 'value':'wind_kmh'},
{'label':'Rain (mm)', 'value':'precip_mm'},
],
value='temperature_C',
clearable=False,
persistence = 'session' )
def create_heatmap_year_finish_time():
dfc = data_finals.sort_values('boatclass').reset_index()
fig = go.Figure(data=go.Heatmap(
x = dfc['year'],
y = dfc['cup'],
z = dfc['finish_time'],
hoverongaps = False,
type = 'heatmap',
#colorscale = 'Viridis',
colorscale = 'inferno_r'
# reversescale=True
))
fig.update_layout(template='plotly_dark', title='Winning times over the years in seconds')
return dcc.Graph(figure = fig, id='race-heatmap')
def create_weather_year_finish_time(weather_value):
dfc = data_finals.sort_values('boatclass').reset_index()
fig = go.Figure(data=go.Heatmap(
x = dfc['year'],
y = dfc['cup'],
z = dfc[weather_value],
hoverongaps = False,
type = 'heatmap',
#colorscale = 'Viridis',
colorscale = 'RdbU',
reversescale=True
))
fig.update_layout(template='plotly_dark', title='Weather over the years')
return dcc.Graph(figure = fig, id='race-weathermap')
app = Dash(__name__,suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME, dbc_css,vizro_bootstrap])
app.layout = dbc.Container([
dbc.Row([
dbc.Col([html.H1('Henley Race Results')
], className='w-75'),
dbc.Col([select_weather_cond], className='w-25')
], style={'marginTop':'2rem'}),
dbc.Row([
dbc.Col([ html.Div( create_heatmap_year_finish_time(), style={'display':'flex', 'flexWrap':'wrap'}),
html.P('Horizontal line: winning time YoY per race, Vertical line: final times in a year. The more yellow, the faster.')]),
dbc.Col([
html.Div(id='weather-chart'),
html.P('More red means, warmer, more wind or more rain')
])
]),
html.Hr(style={'margin':'2rem'}),
dbc.Row([
dbc.Col([html.H2('Compare Boatclasses'),], className='w-50'),
dbc.Col([select_boatclass,], className='w-50')
]),
dbc.Row([
html.Div(id='class-year-times')
]),
dbc.Row([
html.Div(id='cup-info', style={'display':'flex', 'flexWrap':'wrap'})
]),
])
@callback(
Output("weather-chart","children"),
Input("select_weather", "value")
)
def update_weather(value):
if not value:
value = 'temperature_C'
return create_weather_year_finish_time(value)
@callback(
Output("class-year-times", "children"),
Output("cup-info","children"),
Input("select_boatclass", "value")
)
def get_data(boatclass):
if not boatclass:
boatclass = ['M8']
dff = data_finals[data_finals['boatclass'].isin(boatclass)]
cups=dff[dff['boatclass'].isin(boatclass)]['cup'].unique()
fig = go.Figure()
cards = []
for cup in cups:
fig.add_trace(go.Scatter(x=dff[dff['cup']==cup]['year'], y=dff[dff['cup']==cup]['finish_time'],
name=f"{dff[dff['cup']==cup]['boatclass'].iloc[0]} {dff[dff['cup']==cup]['team_origin'].iloc[0]} - {cup}",
mode='lines+markers',
))
#print(dfi[dfi['cup']==cup]['description'])
card = dbc.Card(
[
dbc.CardImg(src=f"{dfi[dfi['cup']==cup]['img_url'].iloc[0]}", top=True),
dbc.CardBody(
[
html.H4(f"{cup}", className="card-title"),
html.P(
f"{dfi[dfi['cup']==cup]['description'].iloc[0]}",
className="card-text",
),
]
),
],className="w-25 mb-3",)
cards.append(card)
fig.update_yaxes(range=[rangemin_finish_time ,rangemax_finish_time])
fig.update_layout(template='plotly_dark')
return dcc.Graph(figure=fig, id = 'races-chart'), cards
# @app.callback(
# Output("race-data", "children"),
# Input("race-heatmap", "clickData"),
# [State('race-heatmap', 'figure')]
# )
# def update_grid(clickData,figure):
# if clickData is None:
# return f"Niets geklikt" # Show all data initially
# # Extract year and sponsorship count from click event
# clicked_year = clickData["points"][0]["x"]
# clicked_race =clicked_year = clickData["points"][0]["y"]
# #curve_number = clickData['points'][0]['curveNumber']
# # trace_name = figure['data'][curve_number]['name']
# # cup_name = trace_name.split(' - ')[-1]
# # print(cup_name)
# # # # Filter dataframe
# # # filtered_df = df[
# # # (df["nea_grant_year"] == clicked_year) &
# # # (df["sponsorships"] == clicked_neacount)
# # # ]
# # show_race = data_finals[(data_finals['year'] == int(clicked_year)) & (data_finals['cup'] == cup_name)].iloc[0]
# #return f"{clicked_year}, trace: {cup_name}"
# return f"{clicked_race}"
if __name__ == "__main__":
app.run(debug=True)