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):
height: auto;
}}
a {{
a {{<
color: black;
transition: color 0.3s ease;
}}
......@@ -163,14 +163,12 @@ def get_competition_page_html(self, competition_id):
<p><a href="../index.html">← Back to World Ranking</a></p>
"""
html_content += """
html_content += f"""
</div>
<!--
<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>
-->
<h2>Results</h2>
<table>
......
File added
......@@ -18,6 +18,7 @@ from world_ranking_list import get_world_ranking_html
# Constants for Elo calculation
K_FACTOR = 15
INITIAL_ELO = 1500 # Starting Elo for all teams
IMAGE_FORMAT = "webp"
def expected_score(rating_a, rating_b):
......@@ -50,6 +51,7 @@ class EloSystem:
raise
with open("header-snippet.html") as header_snippet:
self.header_snippet = header_snippet.read()
self.IMAGE_FORMAT = IMAGE_FORMAT
def load_competition_meta_data(self):
if self.competition_type == "combustion":
......@@ -155,6 +157,9 @@ class EloSystem:
# For each team, compare with teams ranked below them
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 j in range(i + 1, len(teams_in_order)):
team_a = teams_in_order[i]
......@@ -165,29 +170,41 @@ class EloSystem:
# Store post-competition Elo ratings and changes
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']
elo_change = self.teams[team]['delta_elo']
old_rank = old_world_ranking.index(team) + 1
new_rank = 0
# Store in team history
self.teams[team]['history'].append({
'date': date.strftime("%Y-%m-%d"),
'competition': self.get_competition_name(competition_id),
'competition_id': competition_id,
'rank': rank,
'placement': placement,
'participants': len(teams_in_order),
'previous_elo': previous_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
self.competition_history[competition_id]['results'][rank-1][2] = previous_elo
self.competition_history[competition_id]['results'][rank-1][3] = elo_change
self.competition_history[competition_id]['results'][placement-1][2] = previous_elo
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]['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):
"""Update Elo ratings for a pair of teams based on their match outcome"""
# Get current Elo ratings
......@@ -231,8 +248,10 @@ class EloSystem:
self.create_world_ranking()
for competition in self.competition_history:
self.create_competition_page(competition)
#for competition in self.competition_history:
# self.create_competition_page(competition)
pool.map(self.create_competition_page, self.competition_history)
pool.map(self.create_team_page, self.teams)
......@@ -273,6 +292,9 @@ class EloSystem:
"""Create a dedicated page for a specific team"""
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)
# Write the HTML file
......@@ -352,7 +374,7 @@ class EloSystem:
# plt.tight_layout()
# 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()
def create_elo_distribution_chart(self):
......@@ -428,12 +450,11 @@ class EloSystem:
plt.tight_layout()
# 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()
def create_team_elo_history_chart(self, team_name):
"""Create a line chart showing a team's Elo history over time"""
plt.figure(figsize=(10, 6))
team_history = self.teams[team_name]['history']
......@@ -442,13 +463,28 @@ class EloSystem:
dates = [entry['date'] 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
plt.plot(range(len(dates)), elo_values, marker='o', linestyle='-', color='gray')
# Create a second y-axis with the same x-axis
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
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']),
textcoords="offset points",
xytext=(0, 10),
......@@ -456,17 +492,84 @@ class EloSystem:
fontsize=8,
rotation=45)
plt.xticks(range(len(dates)), dates, rotation=45)
plt.ylabel('Elo Rating')
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()
# 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)
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):
self.load_and_process_csv_files()
error_history = []
......
......@@ -149,8 +149,8 @@ def get_team_page_html(self, team_name, team_data, team_competitions):
# Calculate average ranking if available
if team_competitions:
avg_rank = sum(comp['rank'] for comp in team_competitions) / len(team_competitions)
best_rank = min(comp['rank'] for comp in team_competitions)
avg_rank = sum(comp['placement'] for comp in team_competitions) / len(team_competitions)
best_rank = min(comp['placement'] for comp in team_competitions)
html_content += f"""
<div class="stat-box">
......@@ -163,39 +163,45 @@ def get_team_page_html(self, team_name, team_data, team_competitions):
</div>
"""
html_content += """
html_content += f"""
</div>
<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>
<h2>Competition History</h2>
<div style="overflow: scroll;">
<table>
<tr>
<th>Date</th>
<th>Competition</th>
<th>Comp</th>
<th>Placement</th>
<th>Rank Before</th>
<th>Rank After</th>
<th>Rank Change</th>
<th>Elo Before</th>
<th>Elo After</th>
<th>Elo Change</th>
</tr>
""".format(clean_filename(team_name), team_name)
"""
# Add rows for each competition
for comp in team_competitions:
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"""
<tr>
<td>{comp['date']}</td>
<td><a href=../comp/{comp['competition_id']}.html>{comp['competition']}</a></td>
<td>{comp['rank']} / {comp['participants']}</td>
<td><a href=../comp/{comp['competition_id']}.html>{comp['competition']} {comp['date'][2:4]}</a></td>
<td>{comp['placement']} / {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['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>"""
html_content += """
......
......@@ -210,7 +210,7 @@ def get_world_ranking_html(self, sorted_teams):
<p>Rankings based on {len(self.competition_history)} competitions.</p>
<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>
<h2>Current Rankings</h2>
......@@ -234,16 +234,16 @@ def get_world_ranking_html(self, sorted_teams):
<td>{team_data['competitions']}</td>
</tr>"""
html_content += """
html_content += f"""
</table>
<div>
<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 class="timestamp">
<p>Generated on {}</p>
<p>Generated on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
</div>
<div style="display: flex; justify-content: center;">
......@@ -252,6 +252,6 @@ def get_world_ranking_html(self, sorted_teams):
</div>
</body>
</html>
""".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
"""
return html_content
\ No newline at end of file