nise/nise-frontend/src/corelib/components/chart/chart.component.ts

129 lines
4.0 KiB
TypeScript
Raw Normal View History

import {ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges} from '@angular/core';
import {DecimalPipe, NgForOf} from "@angular/common";
import {ChartConfiguration, ChartData, DefaultDataPoint} from "chart.js";
import {NgChartsModule} from "ng2-charts";
import {FormsModule} from "@angular/forms";
@Component({
selector: 'app-chart',
standalone: true,
imports: [
DecimalPipe,
NgChartsModule,
NgForOf,
FormsModule
],
templateUrl: './chart.component.html',
styleUrl: './chart.component.css',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChartComponent {
public barChartOptions: ChartConfiguration<'bar'>['options'] = {
responsive: true,
//@ts-ignore
animations: false,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
};
@Input() title!: string;
@Input() data!: number[];
removeOutliers = true;
groupData = true;
showPercentages = true;
calculateStatistics(): Array<{ name: string, value: number }> {
if (this.data.length === 0) {
return [];
}
let chartData = this.data;
if(this.removeOutliers) {
chartData = this.removeOutliersZScore(this.data);
}
// Assuming data is already without outliers and sorted if necessary
let sortedData = chartData.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 - 1);
let stdDev = Math.sqrt(variance);
let min = sortedData[0];
let max = sortedData[sortedData.length - 1];
const statistics = {
'mean': mean,
'median': median,
'std. dev': stdDev,
'min': min,
'max': max
};
return Object.entries(statistics).map(([name, value]) => ({ name, value }));
}
buildChartData(): ChartData<"bar", DefaultDataPoint<"bar">, any> {
let chartData = this.data;
if(this.removeOutliers) {
chartData = this.removeOutliersZScore(this.data);
}
let sortedData = chartData.slice().sort((a, b) => a - b);
const minData = Math.floor(sortedData[0]);
const maxData = Math.ceil(sortedData[sortedData.length - 1]);
let dataPoints: { value: number; count: number }[] = [];
if (this.groupData) {
const rangeSize = 2;
const groupRanges = Array.from({ length: (maxData - minData) / rangeSize + 1 }, (_, i) => minData + i * rangeSize);
dataPoints = groupRanges.map(rangeStart => {
const rangeEnd = rangeStart + rangeSize;
const countInGroup = sortedData.filter(value => value >= rangeStart && value < rangeEnd).length;
return { value: rangeStart, count: countInGroup };
});
} else {
// Not grouping data, fill with zeros where needed
for (let i = minData; i <= maxData; i++) {
const countInGroup = sortedData.filter(value => Math.round(value) === i).length;
dataPoints.push({ value: i, count: countInGroup });
}
}
const total = dataPoints.reduce((acc, curr) => acc + curr.count, 0);
const labels = this.groupData ? dataPoints.map(({ value }) => `${value}ms to ${value + 2}ms`) : dataPoints.map(({ value }) => `${value}ms`);
const datasets = [{
data: dataPoints.map(({ count }) => this.showPercentages ? (count / total * 100) : count),
label: this.showPercentages ? this.title + " (%)": this.title,
backgroundColor: 'rgba(0,255,41,0.66)',
borderRadius: 5
}];
return { labels, datasets };
}
private removeOutliersZScore(data: number[]): number[] {
const mean = data.reduce((acc, curr) => acc + curr, 0) / data.length;
const stdDev = Math.sqrt(data.reduce((acc, curr) => acc + Math.pow(curr - mean, 2), 0) / data.length);
return data.filter(value => {
const zScore = (value - mean) / stdDev;
return Math.abs(zScore) <= 4;
});
}
}