var submissionKeysToCheck = ['kaggle submissions', 'explicit probabilities', 'inferred probabilities']; var currentYear = (new Date()).getFullYear(); var year = currentYear; var showLive = false; var showOutcomes = false; var size = 'medium'; var mySubmissions = {}; var teamNames = {}; var gameSubmissions = {}; var genderPollIntervalIds = {}; var histograms = {}; var gameIdToRound = {}; var gameIdToOutcome = {}; var gameIdToOdds = {}; var gameIdToAvgScore = {}; var submissionToInfo = {}; var gottenGames = {}; var useGenders = new Set(); var useRounds = new Set(); var params = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), }); function getYearGenderGameEntry(obj, year, gender, gameID) { if(obj.hasOwnProperty(year) && obj[year].hasOwnProperty(gender) && obj[year][gender].hasOwnProperty(gameID)) { return obj[year][gender][gameID]; } return null; } function binData(data) { var histo = {}, x, i, arr = []; var max = 0; var min = 1; var buckets = 40; var step = (max-min)/buckets; // Group down for (i = 0; i < data.length; i++) { x = 100*Math.floor(data[i] / step) * step; if (!histo[x]) { histo[x] = 0; } histo[x]++; } // Make the histo group into an array for (x in histo) { if (histo.hasOwnProperty((x))) { arr.push([parseFloat(x), histo[x]]); } } // Finally, sort the array arr.sort(function (a, b) { return a[0] - b[0]; }); return arr; } function reflowHistograms(currentSize, newSize) { var currentWidth = sizeToWidth(currentSize); var currentHeight = sizeToHeight(currentSize); var newWidth = sizeToWidth(newSize); var newHeight = sizeToHeight(newSize); $('.add-histogram').removeClass('col-sm-' + currentWidth.toString()); $('.add-histogram').addClass('col-sm-' + newWidth.toString()); $('.histogram-close-container').removeClass('col-sm-' + currentWidth.toString()); $('.histogram-close-container').addClass('col-sm-' + newWidth.toString()); for(var containerID in histograms) { $('#' + containerID).css('height', newHeight); $('.add-histogram').css('height', newHeight); } for(var containerID in histograms) { histograms[containerID].reflow(); } } function mergeSubmissionKeys(predictions, keysToCheck) { var combined = {}; for (var key of keysToCheck) { if (predictions.hasOwnProperty(key)) { combined = { ...combined, ...predictions[key], }; } } return { 'special submissions': predictions['special submissions'], 'kaggle submissions': combined, }; } function updateSubmissionLines() { for(var containerID in histograms) { var histogram = histograms[containerID]; for(var j=0; j<    ' + g.toUpperCase() + ' - ' + y.toString() + '    >' }, credits: { enabled: false }, xAxis: [{ title: { text: 'Probability ' + teamName1 + ' beats ' + teamName2 }, floor: 0, ceiling: 100, min: 0, max: 100, endOnTick: true }], yAxis: [{ labels: { enabled: false }, title: { text: '' } }, { labels: { enabled: false }, opposite: true, lineWidth: 0, gridLineWidth: 0, minorGridLineWidth: 0, lineColor: 'transparent', title: { text: '' } }], series: [{ name: 'Histogram', type: 'column', color: histo.color, data: binData(Object.values(gameFileProbs['kaggle submissions'])), index: 0, tooltip: { useHTML: true, headerFormat: '', pointFormatter: function() { var maxPenalty = Math.max(Math.pow(this.x / 100, 2), Math.pow(1-(this.x / 100), 2)); var minPenalty = Math.min(Math.pow(this.x / 100, 2), Math.pow(1-(this.x / 100), 2)); return ' ' + this.x.toFixed(2) + '
Penalty: ' + minPenalty.toFixed(3) + ' or ' + maxPenalty.toFixed(3); } }, pointPadding: 0, groupPadding: 0, pointPlacement: 'on' }], legend: { enabled: false } }; for(var submissionFile in mySubmissions) { if(mySubmissions.hasOwnProperty(submissionFile)) { var label = mySubmissions[submissionFile]['label']; var color = mySubmissions[submissionFile]['color']; var style = mySubmissions[submissionFile]['style']; if(gameFileProbs['special submissions'].hasOwnProperty(submissionFile)) { var pred = gameFileProbs['special submissions'][submissionFile]; options.series.push(createPredictionSeries(pred, label, color, style)); } else if(gameFileProbs['kaggle submissions'].hasOwnProperty(submissionFile)) { var pred = gameFileProbs['kaggle submissions'][submissionFile]; options.series.push(createPredictionSeries(pred, label, color, style)); } } } if(showOutcomes) { var outcome = gameIdToOutcome.hasOwnProperty(gameID) ? gameIdToOutcome[gameID] : null; if(outcome != null) { var label = mySubmissions['']['label']; var color = mySubmissions['']['color']; var style = mySubmissions['']['style']; options.series.push(createPredictionSeries(outcome, label, color, style)); } } if(showLive) { var preds = gameIdToOdds[gameID]; if(preds != null && preds.length > 0) { var pred = preds[preds.length - 1]['prob']; var label = mySubmissions['']['label']; var color = mySubmissions['']['color']; var style = mySubmissions['']['style']; options.series.push(createPredictionSeries(pred, label, color, style)); } } return Highcharts.chart(containerID, options); } function createPredictionSeries(pred, name, color, style) { var maxPenalty = Math.max(Math.pow(pred, 2), Math.pow(1-pred, 2)); var minPenalty = Math.min(Math.pow(pred, 2), Math.pow(1-pred, 2)); pred *= 100; return { name: name, type: 'spline', color: color, dashStyle: style, data: [[pred, 0], [pred, 1]], xAxis: 0, yAxis: 1, index: 1, tooltip: { useHTML: true, headerFormat: '', pointFormat: '' + name + ': ' + pred.toFixed(2) + '%
', footerFormat: 'Penalty: ' + minPenalty.toFixed(3) + ' or ' + maxPenalty.toFixed(3) }, marker: { radius: 0 } }; } async function getGameJson(gameID) { if (!gameSubmissions.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); var request = await $.ajax({ url: '/demo/ncaa-root-for-me/data/' + g + '/' + y.toString() + '/games/' + gameID + '.json' }).done(function(data) { if(data != "") { gameSubmissions[gameID] = JSON.parse(data); } }); } } function setTeams(gender) { $('#team1').html(''); $('#team2').html(''); for(var teamID in teamNames) { if(teamNames.hasOwnProperty(teamID) && (gender == 'men' && teamID < 2000) || (gender == 'women' && teamID > 2000)) { $('#team1').append(''); $('#team2').append(''); } } } async function getTeams(year, gender) { var request = await $.ajax({ url: '/demo/ncaa-root-for-me/data/' + gender + '/' + year.toString() + '/team_names.json' }).done(function(data) { var newTeamNames = JSON.parse(data); teamNames = { ...teamNames, ...newTeamNames, }; }); } function sizeToWidth(sz) { switch(sz) { case 'medium': return 6; case 'large': return 12; case 'small': default: return 4; } } function sizeToHeight(sz) { switch(sz) { case 'medium': return '45%'; case 'large': return '75%'; case 'small': default: return '35%'; } } async function getGames(year, gender) { var request = await $.ajax({ url: '/demo/ncaa-root-for-me/ajax/games.php?gender=' + gender + '&year=' + year.toString() }).done(function(data) { var newGames = JSON.parse(data); gottenGames = { ...gottenGames, ...newGames, } }); } function getInitialGameOdds(gameID) { var initProb = -1; var fallbackProb = -1; var preds = gameIdToOdds[gameID]; for (var i=0; i psum + a, 0); if (totalScore == 0) { initProb = pred['prob']; } else { fallbackProb = pred['prob']; break; } } return initProb != -1 ? initProb : (fallbackProb != -1 ? fallbackProb : 0.5); } function getLatestGameOdds(gameID) { var latestProb = -1; var fallbackProb = -1; var started = false; var preds = gameIdToOdds[gameID]; for (var i=0; i psum + a, 0); started = totalScore > 0; } return latestProb != -1 ? latestProb : 0.5; } function pollGenderOdds(gender) { var pollCall = function() { console.log("Getting " + gender + " odds"); getLatestOdds(gender); } pollCall(); genderPollIntervalIds[gender] = setInterval(pollCall, 60000); } function getLatestOdds(gender) { var request = $.ajax({ url: '/demo/ncaa-root-for-me/ajax/odds.php?gender=' + gender }).done(function(data) { var latestOdds = JSON.parse(data); for(var gameID in latestOdds) { if(latestOdds.hasOwnProperty(gameID)) { gameIdToOdds[gameID] = latestOdds[gameID]; if (!gameIdToOutcome.hasOwnProperty(gameID)) { var predLen = gameIdToOdds[gameID].length; var lastPred = gameIdToOdds[gameID][predLen - 1]; var gameFinished = false; //lastPred['completed']; if (gameFinished) { const [y, g, team1, team2] = gameIdInfo(gameID); var outcome = lastPred['scores'][team1] > lastPred['scores'][team2] ? 1 : 0; gameIdToOutcome[gameID] = outcome; // TODO will this update the chart automagically? } else { if (gameSubmissions.hasOwnProperty(gameID)) { createGameHistogram(gameID); } } } } } }); } async function getGameIdToRound(year, gender) { var request = await $.ajax({ url: '/demo/ncaa-root-for-me/data/' + gender + '/' + year.toString() + '/game_id_to_round.json' }).done(function(data) { var newRounds = JSON.parse(data); gameIdToRound = { ...gameIdToRound, ...newRounds, } }); } async function getGameIdToOutcome(year, gender) { var request = await $.ajax({ url: '/demo/ncaa-root-for-me/data/' + gender + '/' + year.toString() + '/game_id_to_outcome.json' }).done(function(data) { var newOutcomes = JSON.parse(data); gameIdToOutcome = { ...gameIdToOutcome, ...newOutcomes, }; }); } async function createGameHistogram(gameID) { await getGameJson(gameID); const [y, g, team1, team2] = gameIdInfo(gameID); var round = 'none'; if(gameIdToRound.hasOwnProperty(gameID)) { round = gameIdToRound[gameID].toString(); } var containerID = 'histogram-' + gameID.replaceAll('_', '-'); if($('#' + containerID).length == 0) { var width = sizeToWidth(size); var height = sizeToHeight(size); var histoContainer = document.createElement('div'); histoContainer.id = containerID; histoContainer.className = 'histogram-container'; $(histoContainer).css('height', height); $(histoContainer).data('year', y); $(histoContainer).data('gender', g); $(histoContainer).data('game-id', gameID); var histoCloseContainer = document.createElement('div'); histoCloseContainer.className = 'histogram-close-container col-sm-' + width.toString(); $(histoCloseContainer).append(histoContainer); $(histoCloseContainer).append('
Remove
'); $('#collapse-round-' + round + '-' + g + ' .card-body .row').prepend(histoCloseContainer); var count = parseInt($('#card-round-' + round).attr('data-count')); $('#card-round-' + round).attr('data-count', (count+1).toString()); count = parseInt($('#card-round-' + round + '-' + g).attr('data-count')); $('#card-round-' + round + '-' + g).attr('data-count', (count+1).toString()); } histograms[containerID] = drawHistogram(containerID); } function chartsViewHandler() { if(params.csv1 !== null) { $('#my-pred-1').val(params.csv1); } if(params.csv2 !== null) { $('#my-pred-2').val(params.csv2); } $('#settings-modal').modal('show'); $('#show-add-histogram').on('click', function() { $('#add-histogram-modal').modal('show'); }); $('#show-settings').on('click', function() { $('#settings-modal').modal('show'); }); $('#confirm-settings').on('click', async function() { year = parseInt($('#year-select').val()); $('#charts-loading-spinner').removeClass('hidden'); // Update size var newSize = $('#size-group button.active').data('value'); reflowHistograms(size, newSize); size = newSize; // Update submission $('.submission-settings').each(function() { var submissionCheckbox = $(this).find('[name="submission-on"]')[0]; if(submissionCheckbox.checked) { var newSubmissionName = $(this).find('[name="submission-name"]').val(); if (newSubmissionName == '') { showLive = true; } else if (newSubmissionName == '') { showOutcomes = true; } mySubmissions[newSubmissionName] = { label: $(this).find('[name="submission-label"]').val(), color: $(this).find('[name="submission-color"]').val(), style: $(this).find('[name="submission-style"]').val() } } }); updateSubmissionLines(); $('#settings-modal').modal('hide'); var genders = ['men', 'women']; for (var gender of genders) { await getTeams(year, gender); await getGameIdToRound(year, gender); var gameIDs = {}; if (year == currentYear) { await getGames(year, gender); } await getGameIdToOutcome(year, gender); for (var gameID in gameIdToOutcome) { if (gameIdToOutcome.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); await getGameJson(gameID); } } if (year == currentYear && showLive && gender == 'men') { pollGenderOdds(gender); } $('#charts-loading-spinner').addClass('hidden'); for(var gameID in gottenGames) { if(gameIdToRound.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); if (y == year && g == gender) { createGameHistogram(gameID); } } } for(var gameID in gameIdToOutcome) { if(gameIdToOutcome.hasOwnProperty(gameID)) { if (gameIdToRound.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); if (y == year && g == gender && gameSubmissions.hasOwnProperty(gameID)) { createGameHistogram(gameID); } } } } } }); $('#team1').on('change', function() { var team1 = $(this).val(); $('#team2 option:not(.placeholder)').attr('disabled', false); $('#team2 option[value="' + team1.toString() + '"]').attr('disabled', true); }); $('#team2').on('change', function() { var team2 = $(this).val(); $('#team1 option:not(.placeholder)').attr('disabled', false); $('#team1 option[value="' + team2.toString() + '"]').attr('disabled', true); }); $('#add-histogram').on('click', function() { var gender = $('#gender-select').val(); var team1 = parseInt($('#team1').val()); var team2 = parseInt($('#team2').val()); var gameID = year.toString() + '_' + Math.min(team1, team2).toString() + '_' + Math.max(team1, team2).toString(); createGameHistogram(gameID); $('#add-histogram-modal').modal('hide'); }); $('#gender-select').on('change', function() { var g = $(this).val(); setTeams(g); }); $('#size-group').on('click', 'button', function() { $('#size-group button').removeClass('active'); $(this).addClass('active'); }); $('#round-accordion').on('click', '.histogram-close-container .remove-bar', function() { var container = $(this).parent().find('.histogram-container'); var containerID = container.attr('id'); var gameID = container.data('game-id'); const [y, g, team1, team2] = gameIdInfo(gameID); var round = gameIdToRound[gameID]; delete gameSubmissions[gameID]; delete histograms[containerID]; $(this).parent().remove(); var count = parseInt($('#card-round-' + round).attr('data-count')); $('#card-round-' + round).attr('data-count', (count-1).toString()); count = parseInt($('#card-round-' + round + '-' + g).attr('data-count')); $('#card-round-' + round + '-' + g).attr('data-count', (count-1).toString()); if(Object.keys(gameSubmissions).length == 0) { clearInterval(genderPollIntervalIds[g]); } }); } function shouldIncludeGame(gameID) { const [y, g, team1, team2] = gameIdInfo(gameID); var rnd = gameIdToRound[gameID]; return useRounds.has(rnd) && useGenders.has(g); } function gameIdInfo(gameID) { var parts = gameID.split('_'); var year = parseInt(parts[0]); var team1 = parseInt(parts[1]); var team2 = parseInt(parts[2]); var gender; if (team1 > 2000) { gender = 'women'; } else { gender = 'men'; } return [year, gender, team1, team2]; } function getLeaderboard() { var leaderboard = []; for (var subKey in submissionToInfo) { if (submissionToInfo.hasOwnProperty(subKey)) { var subName = submissionToInfo[subKey]['name']; var subType = submissionToInfo[subKey]['subType']; var gameInfos = []; var allGameInfos = submissionToInfo[subKey]['games']; for (var allGameInfo of allGameInfos) { var gameID = allGameInfo['gameID']; if (shouldIncludeGame(gameID)) { gameInfos.push(allGameInfo); } } var avgScore = 0; for (var gameInfo of gameInfos) { var gameID = gameInfo['gameID']; var score = gameInfo['score']; avgScore += score; } if (gameInfos.length > 0) { avgScore /= gameInfos.length; for (var i=0; i 1 ? (others['totalScore'] - score) / (others['totalSubmissions'] - 1) : score; gameInfos[i]['delta'] = score - othersAvgScore; } gameInfos.sort(function(a, b) { if(a['delta'] > b['delta']) { return -1; } else if (a['delta'] < b['delta']) { return 1; } else { return 0; } }); leaderboard.push({ name: subName, subType: subType, score: avgScore, gameInfos: gameInfos, }); } } } leaderboard.sort(function(a, b) { if(a['score'] < b['score']) { return -1; } else if(a['score'] > b['score']) { return 1; } else { return 0; } }); return leaderboard; } function setGenderAndRounds() { useGenders.clear(); $('#gender-checkboxes input[type="checkbox"]:checked').each(function() { useGenders.add($(this).attr('name')); }); useRounds.clear(); $('#round-checkboxes input[type="checkbox"]:checked').each(function() { useRounds.add(parseInt($(this).attr('name'))); }); } function runLeaderboard() { var icons = { 'kaggle': '', 'explicit': '', 'inferred': '', 'special': '', }; setGenderAndRounds(); var leaderboard = getLeaderboard(); $('#leaderboard-accordion .row.card').remove(); var i = 1; for (var entry of leaderboard) { var subType = entry['subType']; // Add entry container var entryCard = document.createElement('div'); entryCard.id = 'card-entry-' + i.toString(); entryCard.className = 'card row'; // Entry Header var cardHeader = document.createElement('div'); cardHeader.id = 'heading-entry-' + i.toString(); cardHeader.className = 'card-header'; $(entryCard).append(cardHeader); var h4 = document.createElement('h4'); $(cardHeader).append(h4); var btn = document.createElement('div'); btn.style.cursor = 'pointer'; btn.setAttribute('aria-expaned', 'false'); btn.setAttribute('data-toggle', 'collapse'); btn.setAttribute('data-target', '#collapse-entry-' + i.toString()); btn.setAttribute('aria-controls', 'collapse-entry-' + i.toString()); btn.innerHTML = '
' + i.toString() + '
' + entry['name'] + ' ' + icons[subType] + '
' + entry['score'].toFixed(6) + '
'; $(h4).append(btn); // Entry Games var cardBodyContainer = document.createElement('div'); cardBodyContainer.id = 'collapse-entry-' + i.toString(); cardBodyContainer.className = 'collapse'; cardBodyContainer.setAttribute('aria-labelledby', cardHeader.id); cardBodyContainer.setAttribute('data-parent', '#leaderboard-accordion'); $(entryCard).append(cardBodyContainer); var gameHTML = '
Game
Your Score
Delta from average
'; for (var gameInfo of entry['gameInfos']) { var gameID = gameInfo['gameID']; const [y, g, team1, team2] = gameIdInfo(gameID); var display = gameInfo['winnerName'] + ' beat ' + gameInfo['loserName']; var color = gameInfo['delta'] > 0 ? 'red' : 'green'; gameHTML += '
' + display + ' (' + g[0] + ')
' + gameInfo['score'].toFixed(6) + '
(' + Math.abs(gameInfo['delta']).toFixed(6) + ')
'; } var cardBody = document.createElement('div'); cardBody.className = 'card-body'; cardBody.innerHTML = gameHTML; $(cardBodyContainer).append(cardBody); $('#leaderboard-accordion').append(entryCard); i++; } } async function leaderboardViewHandler(year) { if(params.year !== null) { year = parseInt(params.year); } $('#gender-checkboxes input[type="checkbox"]').on('change', function() { setGenderAndRounds(); runLeaderboard(); }); $('#round-checkboxes input[type="checkbox"]').on('change', function() { setGenderAndRounds(); runLeaderboard(); }); await getTeams(year, 'men'); await getTeams(year, 'women'); if (year == currentYear) { await getGames(year, 'men'); await getGames(year, 'women'); } await getGameIdToRound(year, 'men'); await getGameIdToRound(year, 'women'); await getGameIdToOutcome(year, 'men'); await getGameIdToOutcome(year, 'women'); for (var gameID in gameIdToOutcome) { if (gameIdToOutcome.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); await getGameJson(gameID); } } var badSubmissions = { 'kaggle_portfolio_final_v1.csv': true, 'submission MM GLM.csv': true, 'submission MM nonGLM.csv': true, }; for (var gameID in gameIdToOutcome) { if (gameIdToOutcome.hasOwnProperty(gameID)) { const [y, g, team1, team2] = gameIdInfo(gameID); var rnd = gameIdToRound[gameID]; if (rnd > 0) { gameIdToAvgScore[gameID] = { totalScore: 0, totalSubmissions: 0, }; var gameFileProbs = mergeSubmissionKeys(gameSubmissions[gameID], submissionKeysToCheck.concat(['special submissions'])); var submissions = {}; for (var subTypeFull in gameSubmissions[gameID]) { if (gameSubmissions[gameID].hasOwnProperty(subTypeFull)) { for (var subName in gameSubmissions[gameID][subTypeFull]) { if (gameSubmissions[gameID][subTypeFull].hasOwnProperty(subName) && !badSubmissions.hasOwnProperty(subName)) { const [y, g, team1, team2] = gameIdInfo(gameID); var minTeamId = Math.min(team1, team2); var maxTeamId = Math.max(team1, team2); var minTeamName = teamNames[minTeamId]; var maxTeamName = teamNames[maxTeamId]; var outcome = gameIdToOutcome[gameID]; var prediction = gameSubmissions[gameID][subTypeFull][subName]; var score = Math.pow(prediction - outcome, 2); gameIdToAvgScore[gameID]['totalScore'] += score; gameIdToAvgScore[gameID]['totalSubmissions'] += 1; //console.log(gameID + ' ' + minTeamName + ' vs ' + maxTeamName + ' ' + score.toString() + ' ' + gameIdToAvgScore[gameID]['totalScore'].toFixed(5) + ' ' + (gameIdToAvgScore[gameID]['totalScore'] / gameIdToAvgScore[gameID]['totalSubmissions']).toFixed(5)); var winnerTeamName = outcome == 1 ? minTeamName : maxTeamName; var loserTeamName = outcome == 1 ? maxTeamName : minTeamName; var subType = subTypeFull.split(' ')[0]; var subKey = subType + '/' + subName; if (!submissionToInfo.hasOwnProperty(subKey)) { submissionToInfo[subKey] = { 'name': subName, 'subType': subType, 'games': [], }; } submissionToInfo[subKey]['games'].push({ gameID: gameID, score: score, winnerName: winnerTeamName, loserName: loserTeamName, }); } } } } } } } $('#leaderboard-loading-spinner').addClass('hidden'); runLeaderboard(); } $(document).ready(function() { if (leaderboard) { leaderboardViewHandler(year); } else { chartsViewHandler(); } });