Skip to content
Snippets Groups Projects
Commit 39ade655 authored by maeries's avatar maeries
Browse files

gradient charts and separated mixed results

parent 75bb4b8b
No related branches found
No related tags found
No related merge requests found
......@@ -6,6 +6,8 @@ from operator import truediv
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.colors import LinearSegmentedColormap
from pathlib import Path
import numpy as np
from datetime import datetime
......@@ -27,6 +29,20 @@ def clean_filename(filename):
# Replace spaces and special characters
return ''.join(c if c.isalnum() else '_' for c in filename).lower()
def hex_to_normalized_rgb(hex_color):
# Remove the '#' symbol if present
hex_color = hex_color.lstrip('#')
# Convert the hex values to integers
red = int(hex_color[0:2], 16)
green = int(hex_color[2:4], 16)
blue = int(hex_color[4:6], 16)
# Normalize the values by dividing by 255
normalized_rgb = (red / 255, green / 255, blue / 255)
return normalized_rgb
class EloSystem:
def __init__(self, csv_directory, output_directory, competition_type):
......@@ -39,6 +55,16 @@ class EloSystem:
self.competition_history = {} # Dictionary to store competition results
self.initialize_output_directory()
self.competition_meta_data = self.load_competition_meta_data()
if self.competition_type == "electric":
self.BORDER_COLOR = "#ffff1e"
self.LINK_COLOR = "#ffc400"
self.LINK_COLOR_HOVER = "#cc9d00"
elif self.competition_type == "combustion":
self.BORDER_COLOR = "#ff8c00"
self.LINK_COLOR = "#ff4500"
self.LINK_COLOR_HOVER = "#cc3700"
else:
raise
def load_competition_meta_data(self):
if self.competition_type == "combustion":
......@@ -62,6 +88,9 @@ class EloSystem:
date_string = self.competition_meta_data[self.competition_meta_data["id"] == int(id)]['date'].values[0]
return datetime.strptime(date_string, "%Y-%m-%d")
def get_competition_type(self, id):
return self.competition_meta_data[self.competition_meta_data["id"] == int(id)]['kind'].values[0].strip()
def load_and_process_csv_files(self):
"""Load all CSV files and process them to calculate Elo ratings"""
csv_files = self.competition_meta_data
......@@ -95,7 +124,9 @@ class EloSystem:
rank = row[3].strip()
team_name = row[0].strip()
kind = row[2].strip()
if kind.lower() == self.competition_type:
# Initialize the team if not seen before
if team_name not in self.teams:
self.teams[team_name] = {
......@@ -131,7 +162,6 @@ class EloSystem:
def update_elo_for_competition(self, teams_in_order, competition_id):
"""Update Elo ratings for all teams in a competition"""
# HIER ÄNDERN, UM EV UND CV AUSEINANDERZUSORTIEREN
# For each team, compare with teams ranked below them
date = self.get_competition_date(competition_id) # Use current date as competition date
......@@ -152,6 +182,7 @@ class EloSystem:
'date': date.strftime("%Y-%m-%d"),
'competition': self.get_competition_name(competition_id),
'rank': rank,
'participants': len(teams_in_order),
'previous_elo': self.teams[team]['elo'],
'new_elo': self.teams[team]['elo'] + self.teams[team]['delta_elo'],
'elo_change': self.teams[team]['delta_elo']
......@@ -228,57 +259,191 @@ class EloSystem:
<title>Formula Student {self.competition_type} Elo Rankings</title>
<style>
body {{
font-family: Arial, sans-serif;
line-height: 1.6;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
min-height: 100vh;
margin: 0;
font-family: 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #ebebeb, #c9c9c9);
animation: gradientAnimation 10s ease infinite;
background-size: 200% 200%;
padding: 20px;
}}
@keyframes gradientAnimation {{
0%, 100% {{
background-position: 0% 50%;
}}
50% {{
background-position: 100% 50%;
}}
}}
.content-container {{
max-width: 1200px;
margin: 0 auto;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
}}
h1, h2 {{
h1 {{
font-size: 40px;
text-transform: uppercase;
text-align: center;
color: #333;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
h2 {{
text-align: center;
color: #555;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
margin-top: 30px;
}}
p {{
text-align: center;
color: #777;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
th, td {{
border: 1px solid #ddd;
padding: 8px;
padding: 12px 15px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
color: #333;
font-weight: bold;
text-transform: uppercase;
border-bottom: 2px solid {self.BORDER_COLOR};
}}
tr {{
background-color: white;
transition: background-color 0.3s ease;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
.chart-container {{
margin-top: 30px;
margin: 30px 0;
display: flex;
justify-content: center;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
background-color: white;
padding: 20px;
box-sizing: border-box;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: #0066cc;
color: {self.LINK_COLOR};
text-decoration: none;
transition: color 0.3s ease;
}}
a:hover {{
text-decoration: underline;
color: {self.LINK_COLOR_HOVER};
text-decoration: none;
}}
.timestamp {{
font-size: 12px;
color: #777;
text-align: center;
margin-top: 20px;
animation: fadeInUp 0.5s ease-out 0.7s forwards;
opacity: 0;
}}
.nav-button {{
margin-top: 30px;
padding: 1em 2em;
font-size: larger;
text-transform: uppercase;
width: 12em;
text-align: center;
border: none;
border-radius: 5px;
background: linear-gradient(135deg, #ffff1e, #ffc400);
color: black;
cursor: pointer;
box-shadow: 0 10px 20px rgba(255, 255, 30, 0.3);
animation: fadeInUp 0.5s ease-out 0.8s forwards;
opacity: 0;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}}
.nav-button:hover {{
box-shadow: 0 15px 30px rgba(255, 255, 30, 0.4);
transform: translateY(-2px);
}}
.nav-button::before {{
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.7s ease;
}}
.nav-button:hover::before {{
left: 100%;
}}
@keyframes fadeInUp {{
from {{
opacity: 0;
transform: translateY(20px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
</style>
</head>
<body>
<h1>Formula Student {self.competition_type} Elo Rankings</h1>
<p>Rankings based on {len(self.competition_history)} competitions.</p>
<div class="content-container">
<h1>Formula Student electric Elo Rankings</h1>
<p>Rankings based on 126 competitions.</p>
<div class="chart-container">
<img src="images/top_teams_elo.png" alt="Top Teams Elo Chart">
......@@ -311,7 +476,14 @@ class EloSystem:
<img src="images/elo_distribution.png" alt="Elo Distribution">
</div>
<p><small>Generated on {}</small></p>
<div class="timestamp">
<p>Generated on {}</p>
</div>
<div style="display: flex; justify-content: center;">
<a href="index.html"><button class="nav-button">Back to Home</button></a>
</div>
</div>
</body>
</html>
""".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
......@@ -349,48 +521,80 @@ class EloSystem:
}}
h1, h2 {{
color: #333;
animation: fadeInUp 0.5s ease-out forwards;
}}
table {{
border-collapse: collapse;
width: 100%;
margin-top: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
}}
th, td {{
border: 1px solid #ddd;
padding: 8px;
padding: 12px 15px;
text-align: left;
}}
th {{
background-color: #f2f2f2;
color: #333;
font-weight: bold;
text-transform: uppercase;
border-bottom: 2px solid {self.BORDER_COLOR};
}}
tr {{
background-color: white;
transition: background-color 0.3s ease;
}}
tr:nth-child(even) {{
background-color: #f9f9f9;
}}
tr:hover {{
background-color: #f5f5f5;
}}
p {{
animation: fadeInUp 0.5s ease-out forwards;
}}
.chart-container {{
margin-top: 30px;
display: flex;
justify-content: center;
width: 100%;
animation: fadeInUp 0.5s ease-out forwards;
opacity: 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
background-color: white;
padding: 20px;
box-sizing: border-box;
}}
.chart-container img {{
max-width: 100%;
height: auto;
}}
a {{
color: #0066cc;
color: {self.LINK_COLOR};
text-decoration: none;
transition: color 0.3s ease;
}}
a:hover {{
text-decoration: underline;
color: {self.LINK_COLOR_HOVER};
text-decoration: none;
}}
.summary-stats {{
display: flex;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
animation: fadeInUp 0.5s ease-out forwards;
}}
.stat-box {{
padding: 15px;
......@@ -417,6 +621,16 @@ class EloSystem:
.negative {{
color: red;
}}
@keyframes fadeInUp {{
from {{
opacity: 0;
transform: translateY(20px);
}}
to {{
opacity: 1;
transform: translateY(0);
}}
}}
</style>
</head>
<body>
......@@ -478,7 +692,7 @@ class EloSystem:
<tr>
<td>{comp['date']}</td>
<td>{comp['competition']}</td>
<td>{comp['rank']}</td>
<td>{comp['rank']} / {comp['participants']}</td>
<td>{int(comp['previous_elo'])}</td>
<td>{int(comp['new_elo'])}</td>
<td class="{change_class}">{'+' if elo_change > 0 else ''}{elo_change:.1f}</td>
......@@ -507,7 +721,8 @@ class EloSystem:
def create_top_teams_chart(self):
"""Create a bar chart of the top teams by Elo rating"""
plt.figure(figsize=(12, 8))
fig = plt.figure(figsize=(12, 8))
ax = fig.add_axes([0.3, 0.1, 0.6, 0.8])
# Get top 15 teams by Elo
top_teams = sorted(self.teams.items(), key=lambda x: x[1]['elo'], reverse=True)[:15]
......@@ -516,16 +731,56 @@ class EloSystem:
elo_ratings = [team[1]['elo'] for team in top_teams]
# Create bar chart
bars = plt.barh(team_names, elo_ratings, color='skyblue')
bars = plt.barh(team_names, elo_ratings, color='none')
# Add Elo values at the end of each bar
for i, bar in enumerate(bars):
plt.text(bar.get_width() + 5, bar.get_y() + bar.get_height() / 2,
f"{int(elo_ratings[i])}", va='center')
# Create custom colormap
colors = [hex_to_normalized_rgb(self.LINK_COLOR), hex_to_normalized_rgb(self.BORDER_COLOR)] # RGB values for #ffff1e and #ffc400
custom_yellow_cmap = LinearSegmentedColormap.from_list('custom_yellow', colors)
for bar in bars:
# Get the coordinates of the bar
x0, y0 = bar.get_xy()
width = bar.get_width()
height = bar.get_height()
# Create a gradient for the bar
gradient = np.linspace(0, 1, 100).reshape(1, -1)
# Get colors from the colormap
colors = custom_yellow_cmap(gradient)
# Set the bar's face color to the gradient
bar.set_facecolor('none') # Remove default color
# Add the gradient rectangle
gradient_rect = patches.Rectangle((x0, y0), width, height,
linewidth=0,
fill=True,
facecolor=custom_yellow_cmap(0.5))
# To create a gradient, add an axial gradient transform
gradient_rect.set_facecolor('none')
ax.add_patch(gradient_rect)
# Create multiple thin rectangles to simulate a gradient
steps = 50
for i in range(steps):
left = x0 + (i/steps) * width
rect_width = width/steps
color = custom_yellow_cmap(i/steps)
rect = patches.Rectangle((left, y0), rect_width, height,
linewidth=0,
color=color)
ax.add_patch(rect)
plt.xlabel('Elo Rating')
plt.title('Top Teams by Elo Rating')
plt.tight_layout()
# plt.tight_layout()
# Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', 'top_teams_elo.png'), dpi=100)
......@@ -538,8 +793,64 @@ class EloSystem:
# Get all Elo ratings
elo_ratings = [team_data['elo'] for team_data in self.teams.values()]
# Create custom yellow gradient colormap (#ffff1e to #ffc400)
colors = [hex_to_normalized_rgb(self.LINK_COLOR), hex_to_normalized_rgb(self.BORDER_COLOR)]
custom_yellow_cmap = LinearSegmentedColormap.from_list('custom_yellow', colors)
# Create histogram
plt.hist(elo_ratings, bins=20, color='skyblue', edgecolor='black')
counts, bin_edges, _ = plt.hist(elo_ratings, bins=20, alpha=0)
# Draw bars with gradients
bin_width = bin_edges[1] - bin_edges[0]
for i in range(len(counts)):
# For each bar in the histogram
left = bin_edges[i]
bottom = 0
width = bin_width
height = counts[i]
# Create the gradient effect by stacking multiple thin rectangles
gradient_steps = 50
for step in range(gradient_steps):
# Calculate position and width of each thin rectangle
rect_left = left
rect_width = width
# Calculate height of this piece of the gradient
rect_bottom = bottom + (step / gradient_steps) * height
rect_height = height / gradient_steps
# Get color from our custom colormap
color = custom_yellow_cmap(step / gradient_steps)
# Add the rectangle patch
rect = patches.Rectangle(
(rect_left, rect_bottom),
rect_width,
rect_height,
linewidth=0,
facecolor=color,
edgecolor=None
)
plt.gca().add_patch(rect)
# Add black edges to each bar
for i in range(len(counts)):
left = bin_edges[i]
width = bin_width
height = counts[i]
rect = patches.Rectangle(
(left, 0),
width,
height,
fill=False,
edgecolor='black',
linewidth=1
)
plt.gca().add_patch(rect)
# Set axis limits to make sure the entire histogram is visible
plt.xlim(bin_edges[0], bin_edges[-1])
plt.ylim(0, max(counts) * 1.1)
plt.xlabel('Elo Rating')
plt.ylabel('Number of Teams')
......@@ -564,7 +875,7 @@ class EloSystem:
elo_values = [entry['new_elo'] for entry in team_history]
# Create line chart
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='blue')
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='gray')
# Add competition labels
for i, entry in enumerate(team_history):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment