Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
1 result

Target

Select target project
  • get-racing-foss/fs-elo
1 result
Select Git revision
  • main
1 result
Show changes

Commits on Source 2

...@@ -98,7 +98,7 @@ def get_competition_page_html(self, competition_id): ...@@ -98,7 +98,7 @@ def get_competition_page_html(self, competition_id):
height: auto; height: auto;
}} }}
a {{ a {{<
color: black; color: black;
transition: color 0.3s ease; transition: color 0.3s ease;
}} }}
...@@ -163,14 +163,12 @@ def get_competition_page_html(self, competition_id): ...@@ -163,14 +163,12 @@ def get_competition_page_html(self, competition_id):
<p><a href="../index.html">← Back to World Ranking</a></p> <p><a href="../index.html">← Back to World Ranking</a></p>
""" """
html_content += """ html_content += f"""
</div> </div>
<!--
<div class="chart-container"> <div class="chart-container">
fancy diagram here <img src="../images/{competition_id}_results.{self.IMAGE_FORMAT}" alt="{self.get_full_competition_name(competition_id)} Results">
</div> </div>
-->
<h2>Results</h2> <h2>Results</h2>
<table> <table>
......
File added
...@@ -18,6 +18,7 @@ from world_ranking_list import get_world_ranking_html ...@@ -18,6 +18,7 @@ from world_ranking_list import get_world_ranking_html
# Constants for Elo calculation # Constants for Elo calculation
K_FACTOR = 15 K_FACTOR = 15
INITIAL_ELO = 1500 # Starting Elo for all teams INITIAL_ELO = 1500 # Starting Elo for all teams
IMAGE_FORMAT = "webp"
def expected_score(rating_a, rating_b): def expected_score(rating_a, rating_b):
...@@ -50,6 +51,7 @@ class EloSystem: ...@@ -50,6 +51,7 @@ class EloSystem:
raise raise
with open("header-snippet.html") as header_snippet: with open("header-snippet.html") as header_snippet:
self.header_snippet = header_snippet.read() self.header_snippet = header_snippet.read()
self.IMAGE_FORMAT = IMAGE_FORMAT
def load_competition_meta_data(self): def load_competition_meta_data(self):
if self.competition_type == "combustion": if self.competition_type == "combustion":
...@@ -155,6 +157,9 @@ class EloSystem: ...@@ -155,6 +157,9 @@ class EloSystem:
# For each team, compare with teams ranked below them # For each team, compare with teams ranked below them
date = self.get_competition_date(competition_id) # Use current date as competition date date = self.get_competition_date(competition_id) # Use current date as competition date
old_world_ranking = sorted(self.teams.items(), key=lambda x: x[1]['elo'], reverse=True)
old_world_ranking = tuple(name for name, _ in old_world_ranking)
for i in range(len(teams_in_order)): for i in range(len(teams_in_order)):
for j in range(i + 1, len(teams_in_order)): for j in range(i + 1, len(teams_in_order)):
team_a = teams_in_order[i] team_a = teams_in_order[i]
...@@ -165,29 +170,41 @@ class EloSystem: ...@@ -165,29 +170,41 @@ class EloSystem:
# Store post-competition Elo ratings and changes # Store post-competition Elo ratings and changes
for team in teams_in_order: for team in teams_in_order:
rank = teams_in_order.index(team) + 1 placement = teams_in_order.index(team) + 1
previous_elo = self.teams[team]['elo'] previous_elo = self.teams[team]['elo']
elo_change = self.teams[team]['delta_elo'] elo_change = self.teams[team]['delta_elo']
old_rank = old_world_ranking.index(team) + 1
new_rank = 0
# Store in team history # Store in team history
self.teams[team]['history'].append({ self.teams[team]['history'].append({
'date': date.strftime("%Y-%m-%d"), 'date': date.strftime("%Y-%m-%d"),
'competition': self.get_competition_name(competition_id), 'competition': self.get_competition_name(competition_id),
'competition_id': competition_id, 'competition_id': competition_id,
'rank': rank, 'placement': placement,
'participants': len(teams_in_order), 'participants': len(teams_in_order),
'previous_elo': previous_elo, 'previous_elo': previous_elo,
'new_elo': self.teams[team]['elo'] + self.teams[team]['delta_elo'], 'new_elo': self.teams[team]['elo'] + self.teams[team]['delta_elo'],
'elo_change': elo_change 'elo_change': elo_change,
'old_rank': old_rank,
'new_rank': new_rank,
'rank_change': new_rank - old_rank
}) })
# Store in competition history # Store in competition history
self.competition_history[competition_id]['results'][rank-1][2] = previous_elo self.competition_history[competition_id]['results'][placement-1][2] = previous_elo
self.competition_history[competition_id]['results'][rank-1][3] = elo_change self.competition_history[competition_id]['results'][placement-1][3] = elo_change
self.teams[team]['elo'] = self.teams[team]['elo'] + self.teams[team]['delta_elo'] self.teams[team]['elo'] = self.teams[team]['elo'] + self.teams[team]['delta_elo']
self.teams[team]['delta_elo'] = 0 self.teams[team]['delta_elo'] = 0
for team in teams_in_order:
new_world_ranking = sorted(self.teams.items(), key=lambda x: x[1]['elo'], reverse=True)
new_world_ranking = tuple(name for name, _ in new_world_ranking)
new_rank = new_world_ranking.index(team) + 1
self.teams[team]['history'][-1]['new_rank'] = new_rank
self.teams[team]['history'][-1]['rank_change'] = new_rank - self.teams[team]['history'][-1]['old_rank']
def update_elo_pair(self, team_a, team_b, score_a, score_b, competition_id, date): def update_elo_pair(self, team_a, team_b, score_a, score_b, competition_id, date):
"""Update Elo ratings for a pair of teams based on their match outcome""" """Update Elo ratings for a pair of teams based on their match outcome"""
# Get current Elo ratings # Get current Elo ratings
...@@ -231,8 +248,10 @@ class EloSystem: ...@@ -231,8 +248,10 @@ class EloSystem:
self.create_world_ranking() self.create_world_ranking()
for competition in self.competition_history: #for competition in self.competition_history:
self.create_competition_page(competition) # self.create_competition_page(competition)
pool.map(self.create_competition_page, self.competition_history)
pool.map(self.create_team_page, self.teams) pool.map(self.create_team_page, self.teams)
...@@ -273,6 +292,9 @@ class EloSystem: ...@@ -273,6 +292,9 @@ class EloSystem:
"""Create a dedicated page for a specific team""" """Create a dedicated page for a specific team"""
print(f"Generating Competition site for {competition_id}") print(f"Generating Competition site for {competition_id}")
# Competition results as bar chart
self.create_competition_result_chart(competition_id)
html_content = get_competition_page_html(self, competition_id) html_content = get_competition_page_html(self, competition_id)
# Write the HTML file # Write the HTML file
...@@ -352,7 +374,7 @@ class EloSystem: ...@@ -352,7 +374,7 @@ class EloSystem:
# plt.tight_layout() # plt.tight_layout()
# Save the chart # Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', 'top_teams_elo.png'), dpi=100) plt.savefig(os.path.join(self.output_directory, 'images', 'top_teams_elo.' + self.IMAGE_FORMAT), dpi=100)
plt.close() plt.close()
def create_elo_distribution_chart(self): def create_elo_distribution_chart(self):
...@@ -428,12 +450,11 @@ class EloSystem: ...@@ -428,12 +450,11 @@ class EloSystem:
plt.tight_layout() plt.tight_layout()
# Save the chart # Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', 'elo_distribution.png'), dpi=100) plt.savefig(os.path.join(self.output_directory, 'images', 'elo_distribution.' + self.IMAGE_FORMAT), dpi=100)
plt.close() plt.close()
def create_team_elo_history_chart(self, team_name): def create_team_elo_history_chart(self, team_name):
"""Create a line chart showing a team's Elo history over time""" """Create a line chart showing a team's Elo history over time"""
plt.figure(figsize=(10, 6))
team_history = self.teams[team_name]['history'] team_history = self.teams[team_name]['history']
...@@ -442,13 +463,28 @@ class EloSystem: ...@@ -442,13 +463,28 @@ class EloSystem:
dates = [entry['date'] for entry in team_history] dates = [entry['date'] for entry in team_history]
elo_values = [entry['new_elo'] for entry in team_history] elo_values = [entry['new_elo'] for entry in team_history]
ranks = [entry['new_rank'] for entry in team_history]
# Create the first plot with elo_values
fig, ax2 = plt.subplots(figsize=(12, 6))
ax2.plot(range(len(dates)), ranks, marker='none', linestyle='-', color='gray', label='Rank')
ax2.set_ylabel('Rank', color='gray')
ax2.tick_params(axis='y', labelcolor='gray')
ax2.invert_yaxis()
ax2.set_xticks(range(len(dates)))
ax2.set_xticklabels(dates, rotation=45, ha='right') # ha='right' helps with alignment
# Create line chart # Create a second y-axis with the same x-axis
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='gray') ax1 = ax2.twinx()
ax1.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='black', label='ELO Values')
ax1.set_xlabel('Dates')
ax1.set_ylabel('ELO Rating', color='black')
ax1.tick_params(axis='y', labelcolor='black')
# Add competition labels # Add competition labels
for i, entry in enumerate(team_history): for i, entry in enumerate(team_history):
plt.annotate(entry['competition'] + f" (#{entry['rank']})", ax1.annotate(entry['competition'] + f" (#{entry['placement']})",
(i, entry['new_elo']), (i, entry['new_elo']),
textcoords="offset points", textcoords="offset points",
xytext=(0, 10), xytext=(0, 10),
...@@ -456,17 +492,84 @@ class EloSystem: ...@@ -456,17 +492,84 @@ class EloSystem:
fontsize=8, fontsize=8,
rotation=45) rotation=45)
plt.xticks(range(len(dates)), dates, rotation=45)
plt.ylabel('Elo Rating')
plt.title(f'{team_name} - Elo Rating History') plt.title(f'{team_name} - Elo Rating History')
plt.grid(True, alpha=0.3) ax2.grid(True, axis='x', alpha=0.3)
ax1.grid(True, alpha=0.3)
plt.tight_layout() plt.tight_layout()
# Save the chart # Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', f'{clean_filename(team_name)}_history.png'), plt.savefig(os.path.join(self.output_directory, 'images', f'{clean_filename(team_name)}_history.' + self.IMAGE_FORMAT),
dpi=100) dpi=100)
plt.close() plt.close()
def create_competition_result_chart(self, competition_id):
"""Create a bar chart of the top teams by Elo rating"""
competition = self.competition_history[competition_id]
team_names = [result[1] for result in competition['results']]
team_names = team_names[::-1]
elo_change = [result[3] for result in competition['results']]
elo_change = elo_change[::-1]
fig = plt.figure(figsize=(12, len(team_names)*0.2175+2))
ax = fig.add_axes([0.3, 0.1, 0.6, 0.8])
# Create bar chart
bars = plt.barh(team_names, elo_change, 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_change[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 Gain')
plt.title('ELo Gain per Team')
# Save the chart
plt.savefig(os.path.join(self.output_directory, 'images', competition_id + '_results.' + self.IMAGE_FORMAT), dpi=100)
plt.close()
def optimize_k(self): def optimize_k(self):
self.load_and_process_csv_files() self.load_and_process_csv_files()
error_history = [] error_history = []
......
...@@ -149,8 +149,8 @@ def get_team_page_html(self, team_name, team_data, team_competitions): ...@@ -149,8 +149,8 @@ def get_team_page_html(self, team_name, team_data, team_competitions):
# Calculate average ranking if available # Calculate average ranking if available
if team_competitions: if team_competitions:
avg_rank = sum(comp['rank'] for comp in team_competitions) / len(team_competitions) avg_rank = sum(comp['placement'] for comp in team_competitions) / len(team_competitions)
best_rank = min(comp['rank'] for comp in team_competitions) best_rank = min(comp['placement'] for comp in team_competitions)
html_content += f""" html_content += f"""
<div class="stat-box"> <div class="stat-box">
...@@ -163,39 +163,45 @@ def get_team_page_html(self, team_name, team_data, team_competitions): ...@@ -163,39 +163,45 @@ def get_team_page_html(self, team_name, team_data, team_competitions):
</div> </div>
""" """
html_content += """ html_content += f"""
</div> </div>
<div class="chart-container"> <div class="chart-container">
<img src="../images/{}_history.png" alt="{} Elo History"> <img src="../images/{clean_filename(team_name)}_history.{self.IMAGE_FORMAT}" alt="{team_name} Elo History">
</div> </div>
<h2>Competition History</h2> <h2>Competition History</h2>
<div style="overflow: scroll;"> <div style="overflow: scroll;">
<table> <table>
<tr> <tr>
<th>Date</th> <th>Comp</th>
<th>Competition</th>
<th>Placement</th> <th>Placement</th>
<th>Rank Before</th>
<th>Rank After</th>
<th>Rank Change</th>
<th>Elo Before</th> <th>Elo Before</th>
<th>Elo After</th> <th>Elo After</th>
<th>Elo Change</th> <th>Elo Change</th>
</tr> </tr>
""".format(clean_filename(team_name), team_name) """
# Add rows for each competition # Add rows for each competition
for comp in team_competitions: for comp in team_competitions:
elo_change = comp['elo_change'] elo_change = comp['elo_change']
change_class = "positive" if elo_change > 0 else "negative" if elo_change < 0 else "" elo_change_class = "positive" if elo_change > 0 else "negative" if elo_change < 0 else ""
rank_change = comp['rank_change']
rank_change_class = "positive" if rank_change < 0 else "negative" if rank_change > 0 else ""
html_content += f""" html_content += f"""
<tr> <tr>
<td>{comp['date']}</td> <td><a href=../comp/{comp['competition_id']}.html>{comp['competition']} {comp['date'][2:4]}</a></td>
<td><a href=../comp/{comp['competition_id']}.html>{comp['competition']}</a></td> <td>{comp['placement']} / {comp['participants']}</td>
<td>{comp['rank']} / {comp['participants']}</td> <td>{comp['old_rank']}</td>
<td>{comp['new_rank']}</td>
<td class="{rank_change_class}">{'+' if rank_change > 0 else ''}{comp['rank_change']}</td>
<td>{int(comp['previous_elo'])}</td> <td>{int(comp['previous_elo'])}</td>
<td>{int(comp['new_elo'])}</td> <td>{int(comp['new_elo'])}</td>
<td class="{change_class}">{'+' if elo_change > 0 else ''}{elo_change:.1f}</td> <td class="{elo_change_class}">{'+' if elo_change > 0 else ''}{elo_change:.1f}</td>
</tr>""" </tr>"""
html_content += """ html_content += """
......
...@@ -210,7 +210,7 @@ def get_world_ranking_html(self, sorted_teams): ...@@ -210,7 +210,7 @@ def get_world_ranking_html(self, sorted_teams):
<p>Rankings based on {len(self.competition_history)} competitions.</p> <p>Rankings based on {len(self.competition_history)} competitions.</p>
<div class="chart-container"> <div class="chart-container">
<img src="images/top_teams_elo.png" alt="Top Teams Elo Chart"> <img src="images/top_teams_elo.{self.IMAGE_FORMAT}" alt="Top Teams Elo Chart">
</div> </div>
<h2>Current Rankings</h2> <h2>Current Rankings</h2>
...@@ -234,16 +234,16 @@ def get_world_ranking_html(self, sorted_teams): ...@@ -234,16 +234,16 @@ def get_world_ranking_html(self, sorted_teams):
<td>{team_data['competitions']}</td> <td>{team_data['competitions']}</td>
</tr>""" </tr>"""
html_content += """ html_content += f"""
</table> </table>
<div> <div>
<div class="chart-container"> <div class="chart-container">
<img src="images/elo_distribution.png" alt="Elo Distribution"> <img src="images/elo_distribution.{self.IMAGE_FORMAT}" alt="Elo Distribution">
</div> </div>
<div class="timestamp"> <div class="timestamp">
<p>Generated on {}</p> <p>Generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
</div> </div>
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
...@@ -252,6 +252,6 @@ def get_world_ranking_html(self, sorted_teams): ...@@ -252,6 +252,6 @@ def get_world_ranking_html(self, sorted_teams):
</div> </div>
</body> </body>
</html> </html>
""".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) """
return html_content return html_content
\ No newline at end of file