Skip to content

Commit

Permalink
Add top cut graphs to tournament show page, render in javascript. (#395)
Browse files Browse the repository at this point in the history
* Add top cut graphs to tournament show page, render in javascript.

* Simplify tournament ids_and_faction_data sql.

* Add newline at end of file.
  • Loading branch information
plural authored Feb 7, 2025
1 parent e97254e commit 58d3512
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 81 deletions.
137 changes: 84 additions & 53 deletions app/models/tournament.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,59 +110,12 @@ def runner_counts
end

def id_and_faction_data
sql = <<~SQL
WITH corp_ids AS (
SELECT
1 AS side,
COALESCE(corp_ids.name, 'Unspecified') as id,
COALESCE(corp_ids.faction, 'unspecified') AS faction
FROM
players AS p
LEFT JOIN identities AS corp_ids
ON p.corp_identity_ref_id = corp_ids.id
WHERE p.tournament_id = ?
),
runner_ids AS (
SELECT
2 AS side,
COALESCE(runner_ids.name, 'Unspecified') as id,
COALESCE(runner_ids.faction, 'unspecified') AS faction
FROM
players AS p
LEFT JOIN identities AS runner_ids
ON p.runner_identity_ref_id = runner_ids.id
WHERE p.tournament_id = ?
)
SELECT side, id, faction, COUNT(*) AS num_ids FROM corp_ids GROUP BY 1,2,3
UNION ALL
SELECT side, id, faction, COUNT(*) AS num_ids FROM runner_ids GROUP BY 1,2,3
SQL
sql = ActiveRecord::Base.sanitize_sql([sql, id, id])

results = {
num_players: 0,
corp: {
ids: {},
factions: {}
},
runner: {
ids: {},
factions: {}
}
}
ActiveRecord::Base.connection.exec_query(sql).each do |row|
side = row['side'] == 1 ? :corp : :runner

# We only need to use 1 side to get the total number of players
results[:num_players] += row['num_ids'] if side == :corp

# Only 1 row per id
results[side][:ids][row['id']] = { count: row['num_ids'], faction: row['faction'] }

# Multiple rows per faction so we need to sum them up
results[side][:factions][row['faction']] ||= 0
results[side][:factions][row['faction']] += row['num_ids']
end
results = build_id_stats(id)
results[:cut] = if stages.last.single_elim? || stages.last.double_elim?
build_id_stats(id, is_cut: true)
else
default_id_stats
end
results
end

Expand Down Expand Up @@ -276,4 +229,82 @@ def create_stage
format: single_sided? ? :single_sided_swiss : :swiss
)
end

# Default data structure for the id and faction data, shared between swiss and elimination stages.
def default_id_stats
{
num_players: 0,
corp: {
ids: {},
factions: {}
},
runner: {
ids: {},
factions: {}
}
}
end

def build_id_stats(id, is_cut: false)
results = default_id_stats

sql = build_id_stats_sql(id, is_cut:)
ActiveRecord::Base.connection.exec_query(sql).each do |row|
side = row['side'] == 1 ? :corp : :runner

# We only need to use 1 side to get the total number of players
results[:num_players] += row['num_ids'] if side == :corp

# Only 1 row per id
results[side][:ids][row['id']] = { count: row['num_ids'], faction: row['faction'] }

# Multiple rows per faction so we need to sum them up
results[side][:factions][row['faction']] ||= 0
results[side][:factions][row['faction']] += row['num_ids']
end
results
end

def build_id_stats_sql(id, is_cut: false)
ids_where = if is_cut
'p.tournament_id = ? AND p.id IN (SELECT player_id FROM registrations WHERE stage_id IN (' \
'SELECT MAX(id) FROM stages WHERE tournament_id = ?))'
else
'p.tournament_id = ?'
end

sql = <<~SQL
WITH corp_ids AS (
SELECT
1 AS side,
COALESCE(corp_ids.name, 'Unspecified') as id,
COALESCE(corp_ids.faction, 'unspecified') AS faction
FROM
players AS p
LEFT JOIN identities AS corp_ids
ON p.corp_identity_ref_id = corp_ids.id
WHERE #{ids_where}
),
runner_ids AS (
SELECT
2 AS side,
COALESCE(runner_ids.name, 'Unspecified') as id,
COALESCE(runner_ids.faction, 'unspecified') AS faction
FROM
players AS p
LEFT JOIN identities AS runner_ids
ON p.runner_identity_ref_id = runner_ids.id
WHERE #{ids_where}
)
SELECT side, id, faction, COUNT(*) AS num_ids FROM corp_ids GROUP BY 1,2,3
UNION ALL
SELECT side, id, faction, COUNT(*) AS num_ids FROM runner_ids GROUP BY 1,2,3
SQL

if is_cut
ActiveRecord::Base.sanitize_sql([sql, id, id, id, id])
else
ActiveRecord::Base.sanitize_sql([sql, id, id])
end
end
end
136 changes: 108 additions & 28 deletions app/views/tournaments/_player_counts.html.slim
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
- if @tournament.rounds.any? || policy(@tournament).edit?
p
h3 Swiss Rounds
.row
.col-md-6
table.table
Expand All @@ -21,27 +23,59 @@

.row.mt-3.dontprint
.col-md-6
table.table
table.table id="swiss_corp_table"
thead
tr
th Corp
th Players
tbody
- @tournament.corp_counts.each do |id, count, total|
tr
td= render id
td= "#{count} (#{"%0.1f" % (count / total.to_f * 100)}%)"
.col-md-6
table.table
table.table id="swiss_runner_table"
thead
tr
th Runner
th Players
tbody
- @tournament.runner_counts.each do |id, count, total|

- if @tournament.stages.size > 1
h3 Elimination Rounds
.row
.col-md-6
table.table
thead
tr
th Corp Factions
tbody
tr
td
div id="cut_corp_faction_chart"
.col-md-6
table.table
thead
tr
th Runner Factions
tbody
tr
td
div id="cut_runner_faction_chart"
.row
.col-md-6
table.table id="cut_corp_table"
thead
tr
th Corp
th Players
tbody

.col-md-6
table.table id="cut_runner_table"
thead
tr
td= render id
td= "#{count} (#{"%0.1f" % (count / total.to_f * 100)}%)"
th Runner
th Players
tbody


javascript:
function drawPieChart(element, series, labels, colors=null) {
const options = {
Expand Down Expand Up @@ -81,6 +115,51 @@
return faction.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
function buildSeriesData(data, colorsMap) {
results = {
series: [],
labels: [],
colors: [],
};
for (faction in data) {
results.series.push(data[faction]);
results.labels.push(displayFaction(faction));
results.colors.push(colorsMap.get(faction));
}
return results;
}
function populateIdRows(data, tableId, numPlayers) {
const ids = [];
for (id in data) {
ids.push({id: id, count: data[id].count, faction: data[id].faction});
}
// Sort by count in descending order, then by id in ascending order
ids.sort((a, b) => {
if (b.count !== a.count) {
return b.count - a.count; // Descending order by count
} else {
return a.id.localeCompare(b.id); // Ascending order by id
}
});
// Add rows to the tableId table
const tableBody = document.querySelector(`#${tableId} tbody`);
ids.forEach(id => {
const row = document.createElement("tr");
const idCell = document.createElement("td");
idCell.innerHTML = `<div class="div ${id.faction}"><i class="fa icon icon-${id.faction == 'unspecified' ? 'interrupt' : id.faction}"></i> ${id.id}</div>`;
row.appendChild(idCell);
const countCell = document.createElement("td");
countCell.innerHTML = `${id.count} (${(id.count / numPlayers * 100).toFixed(1)}%)`;
row.appendChild(countCell);
tableBody.appendChild(row);
});
}
fetch('/tournaments/#{@tournament.id}/id_and_faction_data')
.then(response => response.json())
.then(data => {
Expand All @@ -99,24 +178,25 @@
['weyland-consortium', 'darkgreen'],
['unspecified', 'charcoal'],
]);
let corpFactionSeries = [];
let corpFactionLabels = [];
let corpFactionColors = [];
for (faction in data.corp.factions) {
corpFactionSeries.push(data.corp.factions[faction]);
corpFactionLabels.push(displayFaction(faction));
corpFactionColors.push(factionColors.get(faction));
}
drawPieChart("#corp_faction_chart", corpFactionSeries, corpFactionLabels, corpFactionColors);
let runnerFactionSeries = [];
let runnerFactionLabels = [];
let runnerFactionColors = [];
for (faction in data.runner.factions) {
runnerFactionSeries.push(data.runner.factions[faction]);
runnerFactionLabels.push(displayFaction(faction));
runnerFactionColors.push(factionColors.get(faction));
const corpSeriesData = buildSeriesData(data.corp.factions, factionColors);
drawPieChart("#corp_faction_chart", corpSeriesData.series, corpSeriesData.labels, corpSeriesData.colors);
const runnerSeriesData = buildSeriesData(data.runner.factions, factionColors);
drawPieChart("#runner_faction_chart", runnerSeriesData.series, runnerSeriesData.labels, runnerSeriesData.colors);
populateIdRows(data.corp.ids, "swiss_corp_table", data.num_players);
populateIdRows(data.runner.ids, "swiss_runner_table", data.num_players);
if (data.cut.num_players > 0) {
const cutCorpSeriesData = buildSeriesData(data.cut.corp.factions, factionColors);
drawPieChart("#cut_corp_faction_chart", cutCorpSeriesData.series, cutCorpSeriesData.labels, cutCorpSeriesData.colors);
const cutRunnerSeriesData = buildSeriesData(data.cut.runner.factions, factionColors);
drawPieChart("#cut_runner_faction_chart", cutRunnerSeriesData.series, cutRunnerSeriesData.labels, cutRunnerSeriesData.colors);
populateIdRows(data.cut.corp.ids, "cut_corp_table", data.cut.num_players);
populateIdRows(data.cut.runner.ids, "cut_runner_table", data.cut.num_players);
}
drawPieChart("#runner_faction_chart", runnerFactionSeries, runnerFactionLabels, runnerFactionColors);
})
.catch(error => console.error('Error:', error));
.catch(error => console.error('Error:', error));
5 changes: 5 additions & 0 deletions spec/requests/tournaments_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@
'Sable' => { 'count' => 1, 'faction' => 'criminal' },
'Unspecified' => { 'count' => 1, 'faction' => 'unspecified' }
}
},
'cut' => {
'num_players' => 0,
'corp' => { 'factions' => {}, 'ids' => {} },
'runner' => { 'factions' => {}, 'ids' => {} }
}
)
end
Expand Down

0 comments on commit 58d3512

Please sign in to comment.