import {Component, OnInit, ViewChild} from '@angular/core'; import {HttpClient} from "@angular/common/http"; import {ActivatedRoute, RouterLink} from "@angular/router"; import {Title} from "@angular/platform-browser"; import {DistributionEntry, UserReplayData} from "../replays"; import {environment} from "../../environments/environment"; import {catchError, throwError} from "rxjs"; import {ChartComponent} from "../../corelib/components/chart/chart.component"; import {BaseChartDirective, NgChartsModule} from "ng2-charts"; import {DecimalPipe, NgForOf, NgIf, NgOptimizedImage} from "@angular/common"; import {calculateAccuracy} from "../format"; import {ChartConfiguration} from "chart.js"; @Component({ selector: 'app-view-user-score', standalone: true, imports: [ ChartComponent, NgChartsModule, NgIf, NgForOf, RouterLink, DecimalPipe, NgOptimizedImage ], templateUrl: './view-user-score.component.html', styleUrl: './view-user-score.component.css' }) export class ViewUserScoreComponent implements OnInit { @ViewChild(BaseChartDirective) public chart!: BaseChartDirective; isLoading = false; error: string | null = null; replayData: UserReplayData | 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 ) {} ngOnInit(): void { this.activatedRoute.params.subscribe(params => { this.replayId = params['replayId']; if (this.replayId) { this.loadScoreData(); } }); } private loadScoreData(): void { this.isLoading = true; this.http.get(`${environment.apiUrl}/user-score/${this.replayId}`).pipe( catchError(error => { this.isLoading = false; return throwError(() => new Error('An error occurred with the request: ' + error.message)); }) ).subscribe({ next: (response) => { this.isLoading = false; this.replayData = response; 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; } }); } 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 = { countMiss: 0, count50: 0, count100: 0, count300: 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 = { countMiss: currentEntry.countMiss + nextEntry.countMiss, count50: currentEntry.count50 + nextEntry.count50, count100: currentEntry.count100 + nextEntry.count100, count300: currentEntry.count300 + nextEntry.count300, }; 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.countMiss); datasets[1].push(lastEntry.count50); datasets[2].push(lastEntry.count100); datasets[3].push(lastEntry.count300); } return { labels, datasets }; } protected readonly Object = Object; protected readonly calculateAccuracy = calculateAccuracy; }