From fd5be8f5dc0855aecafe004ce5d8851f1480b745 Mon Sep 17 00:00:00 2001 From: ALEEF02 Date: Wed, 30 Nov 2022 08:49:30 -0500 Subject: [PATCH] Add changes to GlickoHist, Add more stats Sleep service thread for 10 minutes at a time --- .../main/java/ppp/db/controllers/CGames.java | 17 +- .../main/java/ppp/db/controllers/CGlicko.java | 7 +- .../src/main/java/ppp/db/model/OGlicko.java | 11 +- .../src/main/java/ppp/db/model/OUser.java | 6 +- .../ppp/service/ServiceHandlerThread.java | 2 +- MavenBack/src/main/webapp/stats.html | 432 +++++++++++++++++- MavenBack/src/main/webapp/statsStyle.css | 2 +- 7 files changed, 464 insertions(+), 13 deletions(-) diff --git a/MavenBack/src/main/java/ppp/db/controllers/CGames.java b/MavenBack/src/main/java/ppp/db/controllers/CGames.java index 193ee0a..34f4e00 100644 --- a/MavenBack/src/main/java/ppp/db/controllers/CGames.java +++ b/MavenBack/src/main/java/ppp/db/controllers/CGames.java @@ -106,7 +106,16 @@ public static int getNumOfGamesUntilRating() { public static int getNumOfGamesForUser(int userId, StatusEnum.Status status) { return getNumOfGamesForUser(userId, status, false); } - + + /** + * Get the number of games that a user has played + * + * @param userId the id of the user + * @param status the status of the games + * @param userCache whether to user the cache or not + * + * @returns int - the # of games + */ public static int getNumOfGamesForUser(int userId, StatusEnum.Status status, boolean useCache) { int games = 0; if (!useCache) { @@ -223,6 +232,12 @@ public static List getLatestGamesByStatus(StatusEnum.Status status) { return getLatestGamesByStatus(status, 20); } + /** + * Get the latest games that fit to a specified status + * @param status + * @param limit + * @return The list of Game Objects + */ public static List getLatestGamesByStatus(StatusEnum.Status status, int limit) { if (limit < 1 || limit > 200) limit = 20; if (status == StatusEnum.Status.ANY) return getLatestGames(limit); diff --git a/MavenBack/src/main/java/ppp/db/controllers/CGlicko.java b/MavenBack/src/main/java/ppp/db/controllers/CGlicko.java index 1ed80ae..5aade7e 100644 --- a/MavenBack/src/main/java/ppp/db/controllers/CGlicko.java +++ b/MavenBack/src/main/java/ppp/db/controllers/CGlicko.java @@ -106,15 +106,16 @@ private static OGlicko fillRecord(ResultSet rs) throws SQLException { s.rating = rs.getDouble("rating"); s.rd = rs.getDouble("rd"); s.volatility = rs.getDouble("volatility"); + s.ratingCycle = rs.getInt("rating_cycle"); return s; } public static void insert(OGlicko record) { try { record.id = WebDb.get().insert( - "INSERT INTO glicko(userId, date, rating, rd, volatility) " + - "VALUES (?,?,?,?,?)", - record.userId, record.date, record.rating, record.rd, record.volatility); + "INSERT INTO glicko(userId, date, rating, rd, volatility, rating_cycle) " + + "VALUES (?,?,?,?,?,?)", + record.userId, record.date, record.rating, record.rd, record.volatility, record.ratingCycle); } catch (Exception e) { e.printStackTrace(); } diff --git a/MavenBack/src/main/java/ppp/db/model/OGlicko.java b/MavenBack/src/main/java/ppp/db/model/OGlicko.java index d4b7a17..2b632fb 100644 --- a/MavenBack/src/main/java/ppp/db/model/OGlicko.java +++ b/MavenBack/src/main/java/ppp/db/model/OGlicko.java @@ -7,6 +7,7 @@ import java.sql.Timestamp; import java.util.Date; +import java.util.List; public class OGlicko extends AbstractModel { public int id = 0; @@ -15,6 +16,13 @@ public class OGlicko extends AbstractModel { public double rating = GlickoTwo.BASE_RATING; // TODO: Update this to double public double rd = GlickoTwo.BASE_RD; // TODO: Update this to double public double volatility = GlickoTwo.BASE_VOLATILITY; // TODO: Update this to double + public int ratingCycle = 0; + + public void checkRatingCycle() { + if (ratingCycle != 0) return; + List lastCalculatedGame = CGames.getLatestGamesByStatus(StatusEnum.Status.CALCULATED, 1); + ratingCycle = lastCalculatedGame.isEmpty() ? 1 : lastCalculatedGame.get(0).ratingCycle; + } public double getMu() { return (rating - GlickoTwo.BASE_RATING) / GlickoTwo.GLICKO2_CONV; @@ -26,7 +34,8 @@ public double getPhi() { public String toPublicJSON() { return "{\"id\":\"" + id + - "\",\"elo\":" + rating + + "\",\"ratingCycle\":" + ratingCycle + + ",\"elo\":" + rating + ",\"rd\":" + rd + ",\"vol\":" + volatility + ",\"date\":\"" + date + diff --git a/MavenBack/src/main/java/ppp/db/model/OUser.java b/MavenBack/src/main/java/ppp/db/model/OUser.java index 6b8b9cc..1794765 100644 --- a/MavenBack/src/main/java/ppp/db/model/OUser.java +++ b/MavenBack/src/main/java/ppp/db/model/OUser.java @@ -73,7 +73,7 @@ public String toPublicJSON(boolean numGamesPlayedInCycle, boolean withHistories) "\",\"signUpDate\":\"" + signUpDate + "\",\"lastSignIn\":\"" + lastSignIn + "\",\"banned\":\"" + banned + - "\",\"rank\":\"" + rank; + "\",\"rank\":\"" + rank + "\""; if (withHistories) { List glickos = CGlicko.findByUserId(id); @@ -85,11 +85,11 @@ public String toPublicJSON(boolean numGamesPlayedInCycle, boolean withHistories) } } glickoHist += "]"; - rt += "\",\"glickoHist\":" + glickoHist; + rt += ",\"glickoHist\":" + glickoHist; } if (numGamesPlayedInCycle) { - rt += "\",\"gamesPlayedInCycle\":\"" + CGames.getNumOfGamesForUser(id, StatusEnum.Status.ACCEPTED, true) + "\""; + rt += ",\"gamesPlayedInCycle\":\"" + CGames.getNumOfGamesForUser(id, StatusEnum.Status.ACCEPTED, true) + "\""; } rt += "}"; diff --git a/MavenBack/src/main/java/ppp/service/ServiceHandlerThread.java b/MavenBack/src/main/java/ppp/service/ServiceHandlerThread.java index b812e84..67b125e 100644 --- a/MavenBack/src/main/java/ppp/service/ServiceHandlerThread.java +++ b/MavenBack/src/main/java/ppp/service/ServiceHandlerThread.java @@ -43,7 +43,7 @@ public void run() { for (AbstractService instance : instances) { instance.start(); } - sleep(30_000L); // Run every 30 seconds + sleep(600_000L); // Run every 10 minutes } catch (Exception e) { e.printStackTrace(); } diff --git a/MavenBack/src/main/webapp/stats.html b/MavenBack/src/main/webapp/stats.html index 54b2643..2d7e8d1 100644 --- a/MavenBack/src/main/webapp/stats.html +++ b/MavenBack/src/main/webapp/stats.html @@ -48,13 +48,13 @@ users = JSON.parse(usersReq.responseText); google.charts.load("current", { - packages: ["timeline"] + packages: ["timeline", "corechart"] }); google.charts.setOnLoadCallback(drawTimeline); } } - usersReq.open('GET', '/api/users?ranks=50&cached=1'); + usersReq.open('GET', '/api/users?ranks=50&cached=1&withHistory=1'); usersReq.send(); }); @@ -212,6 +212,429 @@ var view = new google.visualization.DataView(dataTable); view.setColumns([0, 2, 3, 4, 5]); chart.draw(view, options); + users = users.sort(function(a, b) { + return a.id - b.id; + }); + drawHistogram(); + } + + function drawHistogram() { + var container = document.getElementById('histogram'); + var chart = new google.visualization.Histogram(container); + var dataTable = new google.visualization.DataTable(); + dataTable.addColumn({ + type: 'string', + id: 'Name' + }); + dataTable.addColumn({ + type: 'number', + id: 'Rating' + }); + + var toAdd = []; + for (var i=0; i < users.length; i++) { + var user = users[i]; + toAdd.push([ + user.username, + parseInt(parseFloat(user.elo)) + ]); + } + dataTable.addRows(toAdd); + + var options = { + title: 'Rating Distribution', + titleTextStyle: { + color: "#FFFFFF" + }, + legend: { position: 'none' }, + vAxis: { + baselineColor: '#FFFFFF', + title: "Number of Players", + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + color: '#555555' + }, + minorGridlines: { + count: 0 + } + }, + hAxis: { + title: "Rating", + baselineColor: '#FFFFFF', + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + count: 0 + }, + }, + width: '100%', + backgroundColor: "#121113", + histogram: { bucketSize: 50 }, + chartArea: { + width: '90%', + height: '75%' + }, + + }; + + chart.draw(dataTable, options); + drawBoxPlot(); + } + + + + function drawBoxPlot() { + var numGlickos = users[0].glickoHist.length; + var toAdd = [new Date(users[0].glickoHist[numGlickos - 1].date)]; + + var data = new google.visualization.DataTable(); + data.addColumn('date', 'Date'); + + for (var i = users.length - 1; i >= 0; i--) { + var user = users[i]; + toAdd[users.length - i] = (parseFloat(user.elo)); + data.addColumn('number', user.username); + } + + data.addColumn({ + id: 'max', + type: 'number', + role: 'interval' + }); + data.addColumn({ + id: 'min', + type: 'number', + role: 'interval' + }); + data.addColumn({ + id: 'firstQuartile', + type: 'number', + role: 'interval' + }); + data.addColumn({ + id: 'mean', + type: 'number', + role: 'interval' + }); + data.addColumn({ + id: 'median', + type: 'number', + role: 'interval' + }); + data.addColumn({ + id: 'thirdQuartile', + type: 'number', + role: 'interval' + }); + + data.addRow(getBoxPlotValues(toAdd)); + + // Starting at the second to last Glicko period & going back in time + for (var j = numGlickos - 2; j >= 0; j--) { + toAdd = [new Date(users[0].glickoHist[j].date)]; + for (var i = users.length - 1; i >= 0; i--) { + var user = users[i]; + var found = false; + for (var e = 0; e < user.glickoHist.length; e++) { + if (user.glickoHist[e].ratingCycle == j + 1) { + found = true; + toAdd[users.length - i] = (parseFloat(user.glickoHist[e].elo)); + break; + } + } + if (!found) { + toAdd[users.length - i] = null; + } + } + data.addRow(getBoxPlotValues(toAdd)); + } + + /** + * Takes an array of input data and returns an + * array of the input data with the box plot + * interval data appended to each row. + */ + function getBoxPlotValues(array) { + var arr = array.slice(1).sort(function(a, b) { + return a - b; + }); + + var temp = arr; + arr = []; + for (var u = 0; u < temp.length; u++) { + if (temp[u] != null) { + arr.push(temp[u]); + } + } + + var max = arr[arr.length - 1]; + var min = arr[0]; + var median = getMedian(arr); + var mean = getMean(arr); + + // First Quartile is the median from lowest to overall median. + var firstQuartile = getMedian(arr.slice(0, parseInt(arr.length / 2) + 1)); + + // Third Quartile is the median from the overall median to the highest. + var thirdQuartile = getMedian(arr.slice(parseInt(arr.length / 2))); + + /*array.splice(1, 0, thirdQuartile); + array.splice(1, 0, median); + array.splice(1, 0, mean); + array.splice(1, 0, firstQuartile); + array.splice(1, 0, min); + array.splice(1, 0, max);*/ + + array.push(max); + array.push(min); + array.push(firstQuartile); + array.push(mean); + array.push(median); + array.push(thirdQuartile); + + return array; + } + + function getMedian(array) { + var length = array.length; + + /* If the array is an even length the + * median is the average of the two + * middle-most values. Otherwise the + * median is the middle-most value. + */ + if (length % 2 === 0) { + var midUpper = length / 2; + var midLower = midUpper - 1; + + return (array[midUpper] + array[midLower]) / 2; + } else { + return array[Math.floor(length / 2)]; + } + } + + function getMean(array) { + var length = array.length; + var total = 0; + + /* If the array is an even length the + * median is the average of the two + * middle-most values. Otherwise the + * median is the middle-most value. + */ + for (var i = 0; i < length; i++) { + total += array[i]; + } + return total / length; + } + + var options = { + interpolateNulls: true, + title: 'Box Plot', + titleTextStyle: { + color: "#FFFFFF" + }, + legend: { + position: 'none' + }, + orientation: "vertical", + lineWidth: 0, + series: [{ + 'color': '#D3362D' + }], + intervals: { + barWidth: 1, + boxWidth: 1, + lineWidth: 2, + style: 'boxes' + }, + interval: { + max: { + style: 'bars', + fillOpacity: 1, + color: '#777777' + }, + min: { + style: 'bars', + fillOpacity: 1, + color: '#777777' + }, + firstQuartile: { + color: '#eb349b' + }, + thirdQuartile: { + color: '#eb349b' + }, + median: { + color: '#eb349b' + }, + mean: { + pointSize: 4, + color: '#FFFFFF', + style: 'points' + } + }, + width: '100%', + backgroundColor: "#121113", + chartArea: { + width: '90%', + height: '75%' + }, + vAxis: { + format: 'M/d/yy', + baselineColor: '#FFFFFF', + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + color: '#555555' + }, + }, + hAxis: { + title: "Rating", + baselineColor: '#FFFFFF', + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + color: '#555555' + }, + minValue: 1000, + maxValue: 1700 + }, + }; + + var chart = new google.visualization.LineChart(document.getElementById('boxplot')); + chart.draw(data, options); + drawTreeDiagram(); + } + + + + + + + + + + function drawTreeDiagram() { + var numGlickos = users[0].glickoHist.length; + var toAdd = [new Date(users[0].glickoHist[numGlickos - 1].date)]; + + var data = new google.visualization.DataTable(); + data.addColumn('date', 'Date'); + + for (var i = users.length - 1; i >= 0; i--) { + var user = users[i]; + toAdd[users.length - i] = (parseFloat(user.elo)); + data.addColumn('number', user.username); + } + + data.addRow(getBoxPlotValues(toAdd)); + + // Starting at the second to last Glicko period & going back in time + for (var j = numGlickos - 2; j >= 0; j--) { + toAdd = [new Date(users[0].glickoHist[j].date)]; + for (var i = users.length - 1; i >= 0; i--) { + var user = users[i]; + var found = false; + for (var e = 0; e < user.glickoHist.length; e++) { + if (user.glickoHist[e].ratingCycle == j + 1) { + found = true; + toAdd[users.length - i] = (parseFloat(user.glickoHist[e].elo)); + break; + } + } + if (!found) { + toAdd[users.length - i] = null; + } + } + data.addRow(getBoxPlotValues(toAdd)); + } + + /** + * Takes an array of input data and returns an + * array of the input data with the box plot + * interval data appended to each row. + */ + function getBoxPlotValues(array) { + + return array; + } + + var options = { + interpolateNulls: true, + title: 'Rating Tree', + titleTextStyle: { + color: "#FFFFFF" + }, + legend: { + position: 'none' + }, + orientation: "vertical", + lineWidth: 3, + series: [{ + 'color': '#D3362D' + }], + intervals: { + barWidth: 1, + boxWidth: 1, + lineWidth: 2, + style: 'boxes' + }, + width: '100%', + backgroundColor: "#121113", + chartArea: { + width: '90%', + height: '75%' + }, + vAxis: { + format: 'M/d/yy', + baselineColor: '#FFFFFF', + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + color: '#555555' + }, + }, + hAxis: { + title: "Rating", + baselineColor: '#FFFFFF', + titleTextStyle: { + color: "#FFFFFF" + }, + textStyle: { + color: "#FFFFFF" + }, + gridlines: { + color: '#555555' + }, + minValue: 1000, + maxValue: 1700 + }, + }; + + var chart = new google.visualization.LineChart(document.getElementById('tree')); + chart.draw(data, options); } @@ -222,7 +645,10 @@ Manage Your Games
-
+
+
+
+
\ No newline at end of file diff --git a/MavenBack/src/main/webapp/statsStyle.css b/MavenBack/src/main/webapp/statsStyle.css index 25ed31d..281b4f2 100644 --- a/MavenBack/src/main/webapp/statsStyle.css +++ b/MavenBack/src/main/webapp/statsStyle.css @@ -1,3 +1,3 @@ -g text { +.timeline g text { fill: #FFFFFF !important } \ No newline at end of file