diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt
index 312a036..b592947 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/Models.kt
@@ -235,8 +235,8 @@ data class ReplayData(
}
data class DistributionEntry(
- val percentageMiss: Double,
- val percentage300: Double,
- val percentage100: Double,
- val percentage50: Double
+ val countMiss: Double,
+ val count300: Double,
+ val count100: Double,
+ val count50: Double
)
\ No newline at end of file
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 ff774e5..2f31e0f 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
@@ -538,7 +538,7 @@ class ScoreService(
var totalHits = 0
judgements.forEach { hit ->
- val error = (hit.error.roundToInt() / 2) * 2
+ val error = hit.error.roundToInt()
val judgementType = hit.type // Assuming this is how you get the judgement type
errorDistribution.getOrPut(error) { mutableMapOf("MISS" to 0, "THREE_HUNDRED" to 0, "ONE_HUNDRED" to 0, "FIFTY" to 0) }
.apply {
@@ -550,10 +550,10 @@ class ScoreService(
return errorDistribution.mapValues { (_, judgementCounts) ->
judgementCounts.values.sum()
DistributionEntry(
- percentageMiss = (judgementCounts.getOrDefault("MISS", 0).toDouble() / totalHits) * 100,
- percentage300 = (judgementCounts.getOrDefault("THREE_HUNDRED", 0).toDouble() / totalHits) * 100,
- percentage100 = (judgementCounts.getOrDefault("ONE_HUNDRED", 0).toDouble() / totalHits) * 100,
- percentage50 = (judgementCounts.getOrDefault("FIFTY", 0).toDouble() / totalHits) * 100
+ countMiss = judgementCounts.getOrDefault("MISS", 0).toDouble(),
+ count300 = judgementCounts.getOrDefault("THREE_HUNDRED", 0).toDouble(),
+ count100 = judgementCounts.getOrDefault("ONE_HUNDRED", 0).toDouble(),
+ count50 = judgementCounts.getOrDefault("FIFTY", 0).toDouble()
)
}
}
@@ -567,7 +567,7 @@ class ScoreService(
var totalHits = 0
judgements.forEach { hit ->
- val error = (hit.error!!.roundToInt() / 2) * 2
+ val error = hit.error!!.roundToInt()
val judgementType = hit.type // Assuming this is how you get the judgement type
errorDistribution.getOrPut(error) { mutableMapOf("Miss" to 0, "300" to 0, "100" to 0, "50" to 0) }
.apply {
@@ -579,10 +579,10 @@ class ScoreService(
return errorDistribution.mapValues { (_, judgementCounts) ->
judgementCounts.values.sum()
DistributionEntry(
- percentageMiss = (judgementCounts.getOrDefault("Miss", 0).toDouble() / totalHits) * 100,
- percentage300 = (judgementCounts.getOrDefault("300", 0).toDouble() / totalHits) * 100,
- percentage100 = (judgementCounts.getOrDefault("100", 0).toDouble() / totalHits) * 100,
- percentage50 = (judgementCounts.getOrDefault("50", 0).toDouble() / totalHits) * 100
+ countMiss = judgementCounts.getOrDefault("MISS", 0).toDouble(),
+ count300 = judgementCounts.getOrDefault("THREE_HUNDRED", 0).toDouble(),
+ count100 = judgementCounts.getOrDefault("ONE_HUNDRED", 0).toDouble(),
+ count50 = judgementCounts.getOrDefault("FIFTY", 0).toDouble()
)
}
}
diff --git a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserScoreService.kt b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserScoreService.kt
index 6ad5837..93d225e 100644
--- a/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserScoreService.kt
+++ b/nise-backend/src/main/kotlin/com/nisemoe/nise/database/UserScoreService.kt
@@ -207,10 +207,10 @@ class UserScoreService(
return errorDistribution.mapValues { (_, judgementCounts) ->
judgementCounts.values.sum()
DistributionEntry(
- percentageMiss = (judgementCounts.getOrDefault("MISS", 0).toDouble() / totalHits) * 100,
- percentage300 = (judgementCounts.getOrDefault("THREE_HUNDRED", 0).toDouble() / totalHits) * 100,
- percentage100 = (judgementCounts.getOrDefault("ONE_HUNDRED", 0).toDouble() / totalHits) * 100,
- percentage50 = (judgementCounts.getOrDefault("FIFTY", 0).toDouble() / totalHits) * 100
+ countMiss = judgementCounts.getOrDefault("MISS", 0).toDouble(),
+ count300 = judgementCounts.getOrDefault("THREE_HUNDRED", 0).toDouble(),
+ count100 = judgementCounts.getOrDefault("ONE_HUNDRED", 0).toDouble(),
+ count50 = judgementCounts.getOrDefault("FIFTY", 0).toDouble()
)
}
}
diff --git a/nise-frontend/src/app/replays.ts b/nise-frontend/src/app/replays.ts
index 1c88de4..d0528d8 100644
--- a/nise-frontend/src/app/replays.ts
+++ b/nise-frontend/src/app/replays.ts
@@ -168,10 +168,10 @@ export interface ReplayData {
}
export interface DistributionEntry {
- percentageMiss: number;
- percentage300: number;
- percentage100: number;
- percentage50: number;
+ countMiss: number;
+ count300: number;
+ count100: number;
+ count50: number;
}
export interface ErrorDistribution {
diff --git a/nise-frontend/src/app/view-score/view-score.component.html b/nise-frontend/src/app/view-score/view-score.component.html
index 869623d..0cb5f93 100644
--- a/nise-frontend/src/app/view-score/view-score.component.html
+++ b/nise-frontend/src/app/view-score/view-score.component.html
@@ -222,15 +222,7 @@
-
0">
-
# hit distribution
-
-
+
+
+
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 10c4952..2f222ff 100644
--- a/nise-frontend/src/app/view-score/view-score.component.ts
+++ b/nise-frontend/src/app/view-score/view-score.component.ts
@@ -11,6 +11,9 @@ import {calculateAccuracy} from "../format";
import {Title} from "@angular/platform-browser";
import {OsuGradeComponent} from "../../corelib/components/osu-grade/osu-grade.component";
import {ChartComponent} from "../../corelib/components/chart/chart.component";
+import {
+ ChartHitDistributionComponent
+} from "../../corelib/components/chart-hit-distribution/chart-hit-distribution.component";
@Component({
selector: 'app-view-score',
@@ -24,7 +27,8 @@ import {ChartComponent} from "../../corelib/components/chart/chart.component";
NgOptimizedImage,
RouterLink,
OsuGradeComponent,
- ChartComponent
+ ChartComponent,
+ ChartHitDistributionComponent
],
templateUrl: './view-score.component.html',
styleUrl: './view-score.component.css'
@@ -39,31 +43,6 @@ export class ViewScoreComponent implements OnInit {
replayData: ReplayData | null = null;
replayId: number | null = null;
- public barChartLegend = true;
- public barChartPlugins = [];
-
- public barChartData: ChartConfiguration<'bar'>['data'] = {
- labels: [],
- datasets: [
- { data: [], label: 'Miss (%)', backgroundColor: 'rgba(255,0,0,0.66)', borderRadius: 5 },
- { data: [], label: '50 (%)', backgroundColor: 'rgba(187,129,33,0.66)', borderRadius: 5 },
- { data: [], label: '100 (%)', backgroundColor: 'rgba(219,255,0,0.8)', borderRadius: 5 },
- { data: [], label: '300 (%)', backgroundColor: 'rgba(0,255,41,0.66)', borderRadius: 5 }
- ],
- };
-
- public barChartOptions: ChartConfiguration<'bar'>['options'] = {
- responsive: true,
- scales: {
- x: {
- stacked: true,
- },
- y: {
- stacked: true
- }
- }
- };
-
constructor(private http: HttpClient,
private activatedRoute: ActivatedRoute,
private title: Title
@@ -111,20 +90,6 @@ export class ViewScoreComponent implements OnInit {
this.title.setTitle(
`${this.replayData.username} on ${this.replayData.beatmap_title} (${this.replayData.beatmap_version})`
)
-
- let errorDistribution = Object.entries(this.replayData.error_distribution);
-
- if (errorDistribution.length >= 1) {
- const sortedEntries = errorDistribution
- .sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
-
- const chartData = this.generateChartData(sortedEntries);
- this.barChartData.labels = chartData.labels;
- for (let i = 0; i < 4; i++) {
- this.barChartData.datasets[i].data = chartData.datasets[i];
- }
- }
-
},
error: (error) => {
this.error = error;
@@ -132,61 +97,6 @@ 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);
-
- return [minKey, maxKey];
- }
-
- private generateChartData(entries: [string, DistributionEntry][]): { labels: string[], datasets: number[][] } {
- const range = this.getChartRange(entries);
- const labels: string[] = [];
- const datasets: number[][] = Array(4).fill(0).map(() => []);
-
- const defaultPercentageValues: DistributionEntry = {
- percentageMiss: 0,
- percentage50: 0,
- percentage100: 0,
- percentage300: 0,
- };
-
- const entriesMap = new Map(entries.map(([key, value]) => [parseInt(key), value]));
-
- for (let key = range[0]; key <= range[1]; key += 2) {
- const endKey = key + 2 <= range[1] ? key + 2 : key + 1;
- labels.push(`${key}ms to ${endKey}ms`);
-
- const currentEntry = entriesMap.get(key) || { ...defaultPercentageValues };
- const nextEntry = key + 1 <= range[1] ? (entriesMap.get(key + 1) || { ...defaultPercentageValues }) : defaultPercentageValues;
-
- const sumEntry: DistributionEntry = {
- percentageMiss: currentEntry.percentageMiss + nextEntry.percentageMiss,
- percentage50: currentEntry.percentage50 + nextEntry.percentage50,
- percentage100: currentEntry.percentage100 + nextEntry.percentage100,
- percentage300: currentEntry.percentage300 + nextEntry.percentage300,
- };
-
- datasets[0].push(sumEntry.percentageMiss);
- datasets[1].push(sumEntry.percentage50);
- datasets[2].push(sumEntry.percentage100);
- datasets[3].push(sumEntry.percentage300);
- }
-
- // Handling the case for an odd last key if needed
- if (range[1] % 2 !== range[0] % 2) {
- const lastEntry = entriesMap.get(range[1]) || { ...defaultPercentageValues };
- labels.push(`${range[1]}ms to ${range[1] + 1}ms`);
- datasets[0].push(lastEntry.percentageMiss);
- datasets[1].push(lastEntry.percentage50);
- datasets[2].push(lastEntry.percentage100);
- datasets[3].push(lastEntry.percentage300);
- }
-
- return { labels, datasets };
- }
protected readonly Object = Object;
protected readonly calculateAccuracy = calculateAccuracy;
diff --git a/nise-frontend/src/app/view-user-score/view-user-score.component.ts b/nise-frontend/src/app/view-user-score/view-user-score.component.ts
index e3c35b6..e3203f9 100644
--- a/nise-frontend/src/app/view-user-score/view-user-score.component.ts
+++ b/nise-frontend/src/app/view-user-score/view-user-score.component.ts
@@ -126,10 +126,10 @@ export class ViewUserScoreComponent implements OnInit {
const datasets: number[][] = Array(4).fill(0).map(() => []);
const defaultPercentageValues: DistributionEntry = {
- percentageMiss: 0,
- percentage50: 0,
- percentage100: 0,
- percentage300: 0,
+ countMiss: 0,
+ count50: 0,
+ count100: 0,
+ count300: 0,
};
const entriesMap = new Map(entries.map(([key, value]) => [parseInt(key), value]));
@@ -142,26 +142,26 @@ export class ViewUserScoreComponent implements OnInit {
const nextEntry = key + 1 <= range[1] ? (entriesMap.get(key + 1) || { ...defaultPercentageValues }) : defaultPercentageValues;
const sumEntry: DistributionEntry = {
- percentageMiss: currentEntry.percentageMiss + nextEntry.percentageMiss,
- percentage50: currentEntry.percentage50 + nextEntry.percentage50,
- percentage100: currentEntry.percentage100 + nextEntry.percentage100,
- percentage300: currentEntry.percentage300 + nextEntry.percentage300,
+ countMiss: currentEntry.countMiss + nextEntry.countMiss,
+ count50: currentEntry.count50 + nextEntry.count50,
+ count100: currentEntry.count100 + nextEntry.count100,
+ count300: currentEntry.count300 + nextEntry.count300,
};
- datasets[0].push(sumEntry.percentageMiss);
- datasets[1].push(sumEntry.percentage50);
- datasets[2].push(sumEntry.percentage100);
- datasets[3].push(sumEntry.percentage300);
+ datasets[0].push(sumEntry.countMiss);
+ datasets[1].push(sumEntry.count50);
+ datasets[2].push(sumEntry.count100);
+ datasets[3].push(sumEntry.count300);
}
// Handling the case for an odd last key if needed
if (range[1] % 2 !== range[0] % 2) {
const lastEntry = entriesMap.get(range[1]) || { ...defaultPercentageValues };
labels.push(`${range[1]}ms to ${range[1] + 1}ms`);
- datasets[0].push(lastEntry.percentageMiss);
- datasets[1].push(lastEntry.percentage50);
- datasets[2].push(lastEntry.percentage100);
- datasets[3].push(lastEntry.percentage300);
+ datasets[0].push(lastEntry.countMiss);
+ datasets[1].push(lastEntry.count50);
+ datasets[2].push(lastEntry.count100);
+ datasets[3].push(lastEntry.count300);
}
return { labels, datasets };
diff --git a/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.css b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.css
new file mode 100644
index 0000000..0c9372a
--- /dev/null
+++ b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.css
@@ -0,0 +1,13 @@
+.chart-statistics {
+ text-align: center;
+ display: flex;
+ justify-content: space-around;
+ list-style-type: none;
+ flex-wrap: wrap;
+}
+
+.chart-statistics li {
+ display: flex;
+ margin-right: 120px;
+ margin-bottom: 10px;
+}
diff --git a/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.html b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.html
new file mode 100644
index 0000000..2196677
--- /dev/null
+++ b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.html
@@ -0,0 +1,21 @@
+
diff --git a/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.ts b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.ts
new file mode 100644
index 0000000..b797f9d
--- /dev/null
+++ b/nise-frontend/src/corelib/components/chart-hit-distribution/chart-hit-distribution.component.ts
@@ -0,0 +1,313 @@
+import {Component, Input, OnChanges, OnInit} from '@angular/core';
+import {ChartConfiguration, ChartData, DefaultDataPoint} from "chart.js";
+import {NgChartsModule} from "ng2-charts";
+import {DistributionEntry, ErrorDistribution} from "../../../app/replays";
+import {FormsModule} from "@angular/forms";
+import {DecimalPipe, NgForOf} from "@angular/common";
+
+@Component({
+ selector: 'app-chart-hit-distribution',
+ standalone: true,
+ imports: [
+ NgChartsModule,
+ FormsModule,
+ DecimalPipe,
+ NgForOf
+ ],
+ templateUrl: './chart-hit-distribution.component.html',
+ styleUrl: './chart-hit-distribution.component.css'
+})
+export class ChartHitDistributionComponent implements OnInit, OnChanges {
+
+ @Input() errorDistribution!: ErrorDistribution;
+ @Input() mods!: string[];
+
+ removeOutliers = true;
+ groupData = true;
+ showPercentages = true;
+
+ public barChartLegend = true;
+ public barChartPlugins = [];
+
+ public barChartOptions: ChartConfiguration<'bar'>['options'] = {
+ responsive: true,
+ scales: {
+ x: {
+ stacked: true,
+ },
+ y: {
+ stacked: true
+ }
+ }
+ };
+
+ ngOnInit(): void {
+ }
+
+ ngOnChanges(): void {
+ const showPercentages = this.showPercentages;
+ this.barChartOptions = {
+ responsive: true,
+ //@ts-ignore
+ animations: false,
+ scales: {
+ x: {
+ stacked: true,
+ },
+ y: {
+ stacked: true,
+ ticks: {
+ callback: function(value, index, ticks) {
+ return showPercentages ? value + '%' : value;
+ }
+ }
+ }
+ }
+ };
+ }
+
+ calculateStatistics(): Array<{ name: string, value: number }> {
+ let { ys} = this.calculateData(this.errorDistribution);
+
+ if(this.removeOutliers) {
+ // Calculate IQR
+ const percentiles = this.percentile(ys, [25, 75]);
+ const iqr = percentiles[1] - percentiles[0];
+
+ // Calculate bounds
+ const lowerBound = percentiles[0] - 1.5 * iqr;
+ const upperBound = percentiles[1] + 1.5 * iqr;
+
+ // Filter outliers
+ ys = ys.filter(y => y > lowerBound && y < upperBound);
+ }
+
+ // Assuming data is already without outliers and sorted if necessary
+ let sortedData = ys.slice().sort((a, b) => a - b);
+
+ let mean = sortedData.reduce((acc, curr) => acc + curr, 0) / sortedData.length;
+ let median = sortedData.length % 2 === 0 ? (sortedData[sortedData.length / 2 - 1] + sortedData[sortedData.length / 2]) / 2 : sortedData[Math.floor(sortedData.length / 2)];
+ let variance = sortedData.reduce((acc, curr) => acc + Math.pow(curr - mean, 2), 0) / (sortedData.length);
+ let stdDev = Math.sqrt(variance);
+ let min = sortedData[0];
+ let max = sortedData[sortedData.length - 1];
+
+ let ur = stdDev * 10;
+ if(this.mods.includes('DT') || this.mods.includes('NC')) {
+ ur /= 1.5;
+ }
+
+ if(this.mods.includes('HT')) {
+ ur /= 0.75;
+ }
+
+ const statistics = {
+ 'mean': mean,
+ 'median': median,
+ 'std. dev': stdDev,
+ 'unstable rate': ur,
+ 'min': min,
+ 'max': max
+ };
+
+ return Object.entries(statistics).map(([name, value]) => ({ name, value }));
+ }
+
+ percentile(data: number[], percentiles: number[]): number[] {
+ // 1. Sort the data
+ const sortedData = data.slice().sort((a, b) => a - b);
+
+ // 2. Calculate indices for percentiles
+ const indices = percentiles.map(p => (p / 100) * (sortedData.length - 1));
+
+ // 3. Extract values, handling potential fractional indices
+ const results = indices.map(index => {
+ const lower = Math.floor(index);
+ const upper = Math.ceil(index);
+ const fraction = index - lower;
+
+ // Basic linear interpolation if index is fractional
+ if (fraction > 0) {
+ return sortedData[lower] + (sortedData[upper] - sortedData[lower]) * fraction;
+ } else {
+ return sortedData[lower];
+ }
+ });
+
+ return results;
+ }
+
+ private calculateTotalErrors(distribution: { countMiss: number; count50: number; count100: number; count300: number }): number {
+ return distribution.countMiss + distribution.count50 + distribution.count100 + distribution.count300;
+ }
+
+ doRemoveOutliers(data: ErrorDistribution): ErrorDistribution {
+ let {errorDetails, ys} = this.calculateData(data);
+
+ // Calculate IQR
+ const q1 = ys[Math.floor((ys.length / 4))];
+ const q3 = ys[Math.floor((ys.length * 3) / 4)];
+ const iqr = q3 - q1;
+
+ // Calculate bounds
+ const lowerBound = q1 - 1.5 * iqr;
+ const upperBound = q3 + 1.5 * iqr;
+
+ // Filter outliers
+ const filteredDetails = errorDetails.filter(detail => detail.x > lowerBound && detail.x < upperBound);
+
+ // Convert back to ErrorDistribution format
+ const filteredData: ErrorDistribution = {};
+ filteredDetails.forEach(detail => {
+ //@ts-ignore
+ if (data[detail.x]) {
+ //@ts-ignore
+ filteredData[detail.x] = data[detail.x];
+ }
+ });
+
+ return filteredData;
+ }
+
+ private calculateData(data: ErrorDistribution) {
+ const errorDetails = Object.entries(data).map(([key, distribution]) => ({
+ x: Number.parseInt(key),
+ y: this.calculateTotalErrors(distribution),
+ }));
+
+ let ys = [];
+ for (let key in data) {
+ let distributionEntry = data[key];
+ for (let i = 0; i < distributionEntry.countMiss; i++) {
+ ys.push(Number.parseInt(key));
+ }
+
+ for (let i = 0; i < distributionEntry.count50; i++) {
+ ys.push(Number.parseInt(key));
+ }
+
+ for (let i = 0; i < distributionEntry.count100; i++) {
+ ys.push(Number.parseInt(key));
+ }
+
+ for (let i = 0; i < distributionEntry.count300; i++) {
+ ys.push(Number.parseInt(key));
+ }
+ }
+ return {errorDetails, ys};
+ }
+
+ buildChartData(): ChartData<"bar", DefaultDataPoint<"bar">, any> {
+ let errorDistribution= this.removeOutliers ? this.doRemoveOutliers(this.errorDistribution) : this.errorDistribution;
+ let errorDistributionArray = Object.entries(errorDistribution);
+
+ let barChartData: ChartConfiguration<'bar'>['data'] = {
+ labels: [],
+ datasets: [
+ { data: [], label: 'Miss' + (this.showPercentages ? ' (%)' : ''), backgroundColor: 'rgba(255,0,0,0.66)', borderRadius: 5 },
+ { data: [], label: '50' + (this.showPercentages ? ' (%)' : ''), backgroundColor: 'rgba(187,129,33,0.66)', borderRadius: 5 },
+ { data: [], label: '100' + (this.showPercentages ? ' (%)' : ''), backgroundColor: 'rgba(219,255,0,0.8)', borderRadius: 5 },
+ { data: [], label: '300' + (this.showPercentages ? ' (%)' : ''), backgroundColor: 'rgba(0,255,41,0.66)', borderRadius: 5 }
+ ],
+ }
+
+ if (errorDistributionArray.length >= 1) {
+ const sortedEntries = errorDistributionArray
+ .sort((a, b) => parseInt(a[0]) - parseInt(b[0]));
+
+ const chartData = this.generateChartData(sortedEntries);
+ barChartData.labels = chartData.labels;
+ for (let i = 0; i < 4; i++) {
+ barChartData.datasets[i].data = chartData.datasets[i];
+ }
+ }
+
+ return barChartData;
+ }
+
+ private getChartRange(entries: [string, DistributionEntry][]): [number, number] {
+ const keys = entries.map(([key, _]) => parseInt(key));
+
+ const minKey = Math.min(...keys);
+ const maxKey = Math.max(...keys);
+
+ return [minKey, maxKey];
+ }
+
+ private generateChartData(entries: [string, DistributionEntry][]): { labels: string[], datasets: number[][] } {
+ const range = this.getChartRange(entries);
+ const labels: string[] = [];
+ const datasets: number[][] = Array(4).fill(0).map(() => []);
+
+ const defaultValues: DistributionEntry = {
+ countMiss: 0,
+ count50: 0,
+ count100: 0,
+ count300: 0,
+ };
+
+ const entriesMap = new Map(entries.map(([key, value]) => [parseInt(key), value]));
+
+ if(this.groupData) {
+ let totalHits = 0;
+ for (let key = range[0]; key <= range[1]; key++) {
+ const entry = entriesMap.get(key) || { ...defaultValues };
+ totalHits += entry.countMiss + entry.count50 + entry.count100 + entry.count300;
+ }
+
+ for (let key = range[0]; key <= range[1]; key += 2) {
+ const endKey = key + 2 <= range[1] ? key + 2 : key + 1;
+ labels.push(`${key}ms to ${endKey}ms`);
+
+ const currentEntry = entriesMap.get(key) || { ...defaultValues };
+ const nextEntry = key + 1 <= range[1] ? (entriesMap.get(key + 1) || { ...defaultValues }) : defaultValues;
+
+ const sumEntry: DistributionEntry = {
+ countMiss: currentEntry.countMiss + nextEntry.countMiss,
+ count50: currentEntry.count50 + nextEntry.count50,
+ count100: currentEntry.count100 + nextEntry.count100,
+ count300: currentEntry.count300 + nextEntry.count300,
+ };
+
+ datasets[0].push(this.showPercentages ? ((sumEntry.countMiss / totalHits) * 100) : sumEntry.countMiss);
+ datasets[1].push(this.showPercentages ? ((sumEntry.count50 / totalHits) * 100) : sumEntry.count50);
+ datasets[2].push(this.showPercentages ? ((sumEntry.count100 / totalHits) * 100) : sumEntry.count100);
+ datasets[3].push(this.showPercentages ? ((sumEntry.count300 / totalHits) * 100) : sumEntry.count300);
+ }
+
+ // Handling the case for an odd last key if needed
+ if (range[1] % 2 !== range[0] % 2) {
+ const lastEntry = entriesMap.get(range[1]) || { ...defaultValues };
+ labels.push(`${range[1]}ms to ${range[1] + 1}ms`);
+ datasets[0].push(this.showPercentages ? ((lastEntry.countMiss / totalHits) * 100) : lastEntry.countMiss);
+ datasets[1].push(this.showPercentages ? ((lastEntry.count50 / totalHits) * 100) : lastEntry.count50);
+ datasets[2].push(this.showPercentages ? ((lastEntry.count100 / totalHits) * 100) : lastEntry.count100);
+ datasets[3].push(this.showPercentages ? ((lastEntry.count300 / totalHits) * 100) : lastEntry.count300);
+ }
+ } else {
+// Calculate totalHits as the sum of all counts across entriesMap before the loop
+ let totalHits = 0;
+ for (let key = range[0]; key <= range[1]; key++) {
+ const entry = entriesMap.get(key) || { ...defaultValues };
+ totalHits += entry.countMiss + entry.count50 + entry.count100 + entry.count300;
+ }
+
+// Then iterate through the range to populate your datasets
+ for (let key = range[0]; key <= range[1]; key++) {
+ labels.push(`${key}ms`);
+
+ const currentEntry = entriesMap.get(key) || { ...defaultValues };
+
+ // Calculate percentage based on totalHits for the entire entriesMap, then push data
+ datasets[0].push(this.showPercentages ? ((currentEntry.countMiss / totalHits) * 100) : currentEntry.countMiss);
+ datasets[1].push(this.showPercentages ? ((currentEntry.count50 / totalHits) * 100) : currentEntry.count50);
+ datasets[2].push(this.showPercentages ? ((currentEntry.count100 / totalHits) * 100) : currentEntry.count100);
+ datasets[3].push(this.showPercentages ? ((currentEntry.count300 / totalHits) * 100) : currentEntry.count300);
+ }
+ }
+
+ return { labels, datasets };
+ }
+
+
+}