Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add top cut graphs to tournament show page, render in javascript. #395

Merged
merged 3 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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