From 4f49a375d0128ddeb7461130ffefd75da83f91cf Mon Sep 17 00:00:00 2001 From: "nise.moe" Date: Sun, 18 Feb 2024 14:50:33 +0100 Subject: [PATCH] Refactor of hit distribution chart to fix incorrect data representation and fix of crash when no slider end timings or keypress timings were present in the score --- .../com/nisemoe/nise/database/ScoreService.kt | 120 +++++++++--------- .../app/view-score/view-score.component.ts | 50 +++++--- 2 files changed, 93 insertions(+), 77 deletions(-) diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt index d2f3237..59375ab 100644 --- a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt +++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/ScoreService.kt @@ -32,72 +32,74 @@ class ScoreService( } fun getCharts(db: Record): List { - // We only return additional charts if the user is an admin. - if (!authService.isAdmin()) { + if (!authService.isAdmin()) return emptyList() + + val charts = mutableListOf() + + val sliderEndReleaseTimes = db.get(SCORES.SLIDEREND_RELEASE_TIMES) + if(sliderEndReleaseTimes != null) { + val sliderEndData = sliderEndReleaseTimes + .filterNotNull() + val sliderFrequencyData: List> = sliderEndData + .groupingBy { it } + .eachCount() + .map { (value, count) -> Pair(value, count / sliderEndData.size.toDouble() * 100) } + + val sliderEndTable = mutableListOf>() + + sliderEndTable.add(Triple( + "Median", + String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN)), + String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED)) + )) + sliderEndTable.add(Triple( + "Std. deviation", + String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION)), + String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED)) + )) + + val sliderEndChart = ReplayDataChart( + title = "slider end release times", + tableSamples = 0, + table = sliderEndTable, + data = sliderFrequencyData + ) + charts.add(sliderEndChart) } - // Slider end chart - val sliderEndData = db.get(SCORES.SLIDEREND_RELEASE_TIMES)!! - .filterNotNull() - val sliderFrequencyData: List> = sliderEndData - .groupingBy { it } - .eachCount() - .map { (value, count) -> Pair(value, count / sliderEndData.size.toDouble() * 100) } + val keypressTimes = db.get(SCORES.KEYPRESSES_TIMES) + if(keypressTimes != null) { + val keypressData = keypressTimes + .filterNotNull() + val keypressFrequencyData: List> = keypressData + .groupingBy { it } + .eachCount() + .map { (value, count) -> Pair(value, count / keypressData.size.toDouble() * 100) } - // Slider end table - val sliderEndTable = mutableListOf>() + val keypressTable = mutableListOf>() - sliderEndTable.add(Triple( - "Median", - String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN)), - String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_MEDIAN_ADJUSTED)) - )) - sliderEndTable.add(Triple( - "Std. deviation", - String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION)), - String.format("%.2f", db.get(SCORES.SLIDEREND_RELEASE_STANDARD_DEVIATION_ADJUSTED)) - )) + keypressTable.add(Triple( + "Median", + String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN)), + String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN_ADJUSTED)) + )) + keypressTable.add(Triple( + "Std. deviation", + String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION)), + String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED)) + )) - val sliderEndChart = ReplayDataChart( - title = "slider end release times", - tableSamples = 0, - table = sliderEndTable, - data = sliderFrequencyData - ) - - // -------------------- + val keypressChart = ReplayDataChart( + title = "keypress release times", + tableSamples = 0, + table = keypressTable, + data = keypressFrequencyData + ) + charts.add(keypressChart) + } - // Slider end chart - val keypressData = db.get(SCORES.KEYPRESSES_TIMES)!! - .filterNotNull() - val keypressFrequencyData: List> = keypressData - .groupingBy { it } - .eachCount() - .map { (value, count) -> Pair(value, count / keypressData.size.toDouble() * 100) } - - // Slider end table - val keypressTable = mutableListOf>() - - keypressTable.add(Triple( - "Median", - String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN)), - String.format("%.2f", db.get(SCORES.KEYPRESSES_MEDIAN_ADJUSTED)) - )) - keypressTable.add(Triple( - "Std. deviation", - String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION)), - String.format("%.2f", db.get(SCORES.KEYPRESSES_STANDARD_DEVIATION_ADJUSTED)) - )) - - val keypressChart = ReplayDataChart( - title = "keypress release times", - tableSamples = 0, - table = keypressTable, - data = keypressFrequencyData - ) - - return listOf(sliderEndChart, keypressChart) + return charts } fun getReplayData(replayId: Long): ReplayData? { diff --git a/nise-frontend/src/app/view-score/view-score.component.ts b/nise-frontend/src/app/view-score/view-score.component.ts index 53cf3f0..9862317 100644 --- a/nise-frontend/src/app/view-score/view-score.component.ts +++ b/nise-frontend/src/app/view-score/view-score.component.ts @@ -155,28 +155,27 @@ export class ViewScoreComponent implements OnInit { private getChartRange(entries: [string, DistributionEntry][]): [number, number] { const keys = entries.map(([key, _]) => parseInt(key)); + const minKey = Math.min(...keys); const maxKey = Math.max(...keys); - const absoluteMax = Math.max(Math.abs(minKey), Math.abs(maxKey)); - return [absoluteMax * -1, absoluteMax]; + return [minKey, maxKey]; } private generateLabelsFromEntries(entries: [string, DistributionEntry][]): string[] { const range = this.getChartRange(entries); - const entriesMap = new Map(entries.map(([key, value]) => [parseInt(key), value])); - - const filledEntries = []; - for (let key = range[0]; key <= range[1]; key++) { - filledEntries.push([key.toString(), entriesMap.get(key) || 0]); + const labelEntries = []; + for (let key = range[0]; key <= range[1]; key += 2) { + const endKey = key + 2; + labelEntries.push(`${key}ms to ${endKey}ms`); } - return filledEntries.map(([key, _]) => { - const start = parseInt(String(key)); - const end = start + 2; - return `${start}ms to ${end}ms`; - }); + if (range[1] % 2 !== range[0] % 2) { + labelEntries.push(`${range[1]}ms to ${range[1] + 1}ms`); + } + + return labelEntries; } private generateChartDataFromEntries(entries: [string, DistributionEntry][], i: number): number[] { @@ -191,21 +190,36 @@ export class ViewScoreComponent implements OnInit { const entriesMap = new Map(entries.map(([key, value]) => [parseInt(key), value])); - const filledEntries: [number, DistributionEntry][] = []; - for (let key = range[0]; key <= range[1]; key++) { - filledEntries.push([key, entriesMap.get(key) || { ...defaultPercentageValues }]); + const chartData = []; + for (let key = range[0]; key <= range[1]; key += 2) { + const currentEntry = entriesMap.get(key) || { ...defaultPercentageValues }; + const nextEntry = entriesMap.get(key + 1) || { ...defaultPercentageValues }; + + const sumEntry: DistributionEntry = { + percentageMiss: currentEntry.percentageMiss + nextEntry.percentageMiss, + percentage50: currentEntry.percentage50 + nextEntry.percentage50, + percentage100: currentEntry.percentage100 + nextEntry.percentage100, + percentage300: currentEntry.percentage300 + nextEntry.percentage300, + }; + + chartData.push(sumEntry); + } + + // Handle the case where the last key is not included because of an odd number of total keys + if (range[1] % 2 !== range[0] % 2) { + const lastEntry = entriesMap.get(range[1]) || { ...defaultPercentageValues }; + chartData.push(lastEntry); } const propertyMap = ['percentageMiss', 'percentage50', 'percentage100', 'percentage300']; - if (i >= 0 && i < propertyMap.length) { - return filledEntries.map(([, value]) => value[propertyMap[i] as keyof DistributionEntry]); + return chartData.map(entry => entry[propertyMap[i] as keyof DistributionEntry]); } return []; } protected readonly Object = Object; - protected readonly calculateAccuracy = calculateAccuracy; + }