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

This commit is contained in:
nise.moe 2024-02-18 14:50:33 +01:00
parent 01bd4e4948
commit 4f49a375d0
2 changed files with 93 additions and 77 deletions

View File

@ -32,72 +32,74 @@ class ScoreService(
}
fun getCharts(db: Record): List<ReplayDataChart> {
// We only return additional charts if the user is an admin.
if (!authService.isAdmin()) {
if (!authService.isAdmin())
return emptyList()
val charts = mutableListOf<ReplayDataChart>()
val sliderEndReleaseTimes = db.get(SCORES.SLIDEREND_RELEASE_TIMES)
if(sliderEndReleaseTimes != null) {
val sliderEndData = sliderEndReleaseTimes
.filterNotNull()
val sliderFrequencyData: List<Pair<Double, Double>> = sliderEndData
.groupingBy { it }
.eachCount()
.map { (value, count) -> Pair(value, count / sliderEndData.size.toDouble() * 100) }
val sliderEndTable = mutableListOf<Triple<String, String, String>>()
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<Pair<Double, Double>> = 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<Pair<Double, Double>> = keypressData
.groupingBy { it }
.eachCount()
.map { (value, count) -> Pair(value, count / keypressData.size.toDouble() * 100) }
// Slider end table
val sliderEndTable = mutableListOf<Triple<String, String, String>>()
val keypressTable = mutableListOf<Triple<String, String, String>>()
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<Pair<Double, Double>> = keypressData
.groupingBy { it }
.eachCount()
.map { (value, count) -> Pair(value, count / keypressData.size.toDouble() * 100) }
// Slider end table
val keypressTable = mutableListOf<Triple<String, String, String>>()
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? {

View File

@ -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<number, DistributionEntry>(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;
}