import pandas as pd
import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
from dash.dependencies import Input, Output
# Load and preprocess data
df = pd.read_csv("henley_results_cleaned.csv")
df['date'] = pd.to_datetime(df['date'], errors='coerce')
df['Year'] = df['date'].dt.year
df['time'] = pd.to_timedelta(df['time'], errors='coerce')
# App init with dark theme
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.DARKLY], suppress_callback_exceptions=True)
# Define custom color palette
color_palette = ['#00FF9C', '#B6FFA1', '#FEFFA7', '#FFE700', 'green']
# Layout
app.layout = dbc.Container([
html.H2(
"Henley Royal Regatta Dashboard",
className="text-center my-4",
style={
'color': '#00FF9C',
'fontFamily': "'Courier New', monospace" # Modified font
}
),
# Filter Row
dbc.Row([
dbc.Col([
html.Label("Select Year:", className="text-light mb-4"),
dcc.Dropdown(
id='year-dropdown',
options=[{'label': 'All', 'value': 'All'}] +
[{'label': str(year), 'value': year} for year in sorted(df['Year'].unique())],
multi=True,
value=['All'],
className="mb-3",
style={
'width': '400px',
'color': 'green'
}
)
], width="auto", className="d-flex justify-content-start")
], className="mb-4"),
# Charts
dbc.Row([
dbc.Col(dcc.Graph(id="bar-chart"), width=12, className="mb-4 shadow")
]),
dbc.Row([
dbc.Col(dcc.Graph(id="treemap-chart"), width=6, className="mb-4 shadow"),
dbc.Col(dcc.Graph(id="donut-chart"), width=6, className="mb-4 shadow")
])
], fluid=True, className="p-4")
# Callback
@app.callback(
[Output('treemap-chart', 'figure'),
Output('donut-chart', 'figure'),
Output('bar-chart', 'figure')],
[Input('year-dropdown', 'value')]
)
def update_charts(selected_years):
if 'All' in selected_years:
selected_years = df['Year'].unique()
filtered_df = df[df['Year'].isin(selected_years)]
races_per_year = filtered_df.groupby("Year").size().reset_index(name="Races")
club_races = filtered_df.groupby(['Year', 'winning_club']).size().reset_index(name="Races")
top_clubs = filtered_df['winning_club'].value_counts().nlargest(5).reset_index()
top_clubs.columns = ['Club', 'Wins']
# Bar Chart
bar_fig = go.Figure()
for i, club in enumerate(top_clubs['Club']):
club_data = club_races[club_races['winning_club'] == club]
bar_fig.add_trace(
go.Bar(
x=club_data['Year'],
y=club_data['Races'],
name=club,
text=club_data['Races'],
hoverinfo="x+y+text",
marker=dict(color=color_palette[i % len(color_palette)])
)
)
bar_fig.update_layout(
title=dict(
text="Races by Year and Winning Club",
x=0.0,
xanchor='left'
),
barmode="group",
template="plotly_dark",
xaxis_title="",
yaxis_title="Number of Races",
annotations=[
dict(
x=2020,
y=18,
xref='x',
yref='y',
text='2020 paused due to COVID',
showarrow=True,
arrowhead=5,
ax=0,
ay=-100,
font=dict(color='red', size=16),
bgcolor='rgba(0,0,0,0.6)',
bordercolor='red',
borderwidth=1
)
],
legend=dict(
orientation="h",
yanchor="bottom",
y=1.1,
xanchor="center",
x=0.5
)
)
# Treemap
verdict_races = filtered_df.groupby('verdict').size().reset_index(name="Races")
treemap_fig = go.Figure(go.Treemap(
labels=verdict_races['verdict'],
parents=[""] * len(verdict_races),
values=verdict_races['Races'],
hoverinfo="label+value",
marker=dict(colors=verdict_races['Races'], colorscale="greens")
))
treemap_fig.update_layout(
title="Treemap of Races by Verdict",
template="plotly_dark"
)
# Donut Chart
destination_races = filtered_df.groupby('winner_station').size().reset_index(name="Races")
donut_fig = go.Figure(go.Pie(
labels=destination_races['winner_station'],
values=destination_races['Races'],
hole=0.7,
hoverinfo="label+value",
textinfo="label+value",
marker=dict(colors=color_palette)
))
donut_fig.update_layout(
title="Distribution of Races by Destination (Winner Station)",
template="plotly_dark"
)
return treemap_fig, donut_fig, bar_fig
if __name__ == '__main__':
app.run(debug=True)