-import { AfterViewInit, Component, Input, OnChanges, OnInit, ViewChild } from '@angular/core';
+import { AfterViewInit, Component, Input, OnChanges, ViewChild } from '@angular/core';
import { CssHelper } from '~/app/shared/classes/css-helper';
import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
import { FormatterService } from '~/app/shared/services/formatter.service';
import { BaseChartDirective, PluginServiceGlobalRegistrationAndOptions } from 'ng2-charts';
import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe';
+import { NumberFormatterService } from '~/app/shared/services/number-formatter.service';
@Component({
selector: 'cd-dashboard-area-chart',
templateUrl: './dashboard-area-chart.component.html',
styleUrls: ['./dashboard-area-chart.component.scss']
})
-export class DashboardAreaChartComponent implements OnInit, OnChanges, AfterViewInit {
+export class DashboardAreaChartComponent implements OnChanges, AfterViewInit {
@ViewChild(BaseChartDirective) chart: BaseChartDirective;
@Input()
chartTitle: string;
@Input()
- maxValue?: any;
+ maxValue?: number;
@Input()
dataUnits: string;
@Input()
- data: any;
+ data: Array<[number, string]>;
@Input()
- data2?: any;
+ data2?: Array<[number, string]>;
@Input()
- label: any;
+ label: string;
@Input()
- label2?: any;
+ label2?: string;
currentDataUnits: string;
currentData: number;
currentDataUnits2?: string;
currentData2?: number;
+ chartDataUnits: string;
+
chartData: any = {
dataset: [
{
private dimlessBinary: DimlessBinaryPipe,
private dimlessBinaryPerSecond: DimlessBinaryPerSecondPipe,
private dimlessPipe: DimlessPipe,
- private formatter: FormatterService
+ private formatter: FormatterService,
+ private numberFormatter: NumberFormatterService
) {}
- ngOnInit(): void {
- this.currentData = Number(
- this.chartData.dataset[0].data[this.chartData.dataset[0].data.length - 1].y
- );
- if (this.data2) {
- this.currentData2 = Number(
- this.chartData.dataset[1].data[this.chartData.dataset[1].data.length - 1].y
- );
- }
+ ngOnChanges(): void {
+ this.updateChartData();
}
- ngOnChanges(): void {
+ ngAfterViewInit(): void {
+ this.updateChartData();
+ }
+
+ private updateChartData(): void {
if (this.data) {
this.setChartTicks();
this.chartData.dataset[0].data = this.formatData(this.data);
this.data2[this.data2.length - 1][1]
).split(' ');
}
- }
-
- ngAfterViewInit(): void {
- if (this.data) {
- this.setChartTicks();
+ if (this.chart) {
+ this.chart.chart.update();
}
}
let formattedData = {};
formattedData = array.map((data: any) => ({
x: data[0] * 1000,
- y: Number(this.convertUnits(data[1]).replace(/[^\d,.]+/g, ''))
+ y: Number(this.convertToChartDataUnits(data[1]).replace(/[^\d,.]+/g, ''))
}));
return formattedData;
}
+ private convertToChartDataUnits(data: any): any {
+ let dataWithUnits: string = '';
+ if (this.chartDataUnits) {
+ if (this.dataUnits === 'B') {
+ dataWithUnits = this.numberFormatter.formatBytesFromTo(
+ data,
+ this.dataUnits,
+ this.chartDataUnits
+ );
+ } else if (this.dataUnits === 'B/s') {
+ dataWithUnits = this.numberFormatter.formatBytesPerSecondFromTo(
+ data,
+ this.dataUnits,
+ this.chartDataUnits
+ );
+ } else if (this.dataUnits === 'ms') {
+ dataWithUnits = this.numberFormatter.formatSecondsFromTo(
+ data,
+ this.dataUnits,
+ this.chartDataUnits
+ );
+ } else {
+ dataWithUnits = this.numberFormatter.formatUnitlessFromTo(
+ data,
+ this.dataUnits,
+ this.chartDataUnits
+ );
+ }
+ }
+ return dataWithUnits;
+ }
+
private convertUnits(data: any): any {
- let dataWithUnits: string;
- if (this.dataUnits === 'bytes') {
+ let dataWithUnits: string = '';
+ if (this.dataUnits === 'B') {
dataWithUnits = this.dimlessBinary.transform(data);
- } else if (this.dataUnits === 'bytesPerSecond') {
+ } else if (this.dataUnits === 'B/s') {
dataWithUnits = this.dimlessBinaryPerSecond.transform(data);
} else if (this.dataUnits === 'ms') {
dataWithUnits = this.formatter.format_number(data, 1000, ['ms', 's']);
}
private setChartTicks() {
- if (this.chart && this.maxValue) {
- let [maxValue, maxValueDataUnits] = this.convertUnits(this.maxValue).split(' ');
- this.chart.chart.options.scales.yAxes[0].ticks.suggestedMax = maxValue;
- this.chart.chart.options.scales.yAxes[0].ticks.suggestedMin = 0;
- this.chart.chart.options.scales.yAxes[0].ticks.stepSize = Number((maxValue / 2).toFixed(0));
- this.chart.chart.options.scales.yAxes[0].ticks.callback = (value: any) => {
- if (value === 0) {
- return null;
- }
- return this.fillString(`${value} ${maxValueDataUnits}`);
- };
- this.chart.chart.update();
- } else if (this.chart && this.data) {
- let maxValue = 0,
- maxValueDataUnits = '';
+ if (!this.chart) {
+ return;
+ }
+
+ let maxValue = 0;
+ let maxValueDataUnits = '';
+ let extraRoom = 1.2;
+
+ if (this.maxValue) {
+ extraRoom = 1.0;
+ [maxValue, maxValueDataUnits] = this.convertUnits(this.maxValue).split(' ');
+ } else if (this.data) {
+ extraRoom = 1.2;
let maxValueData = Math.max(...this.data.map((values: any) => values[1]));
if (this.data2) {
- var maxValueData2 = Math.max(...this.data2.map((values: any) => values[1]));
- [maxValue, maxValueDataUnits] = this.convertUnits(
- Math.max(maxValueData, maxValueData2)
- ).split(' ');
+ let maxValueData2 = Math.max(...this.data2.map((values: any) => values[1]));
+ maxValue = Math.max(maxValueData, maxValueData2);
} else {
- [maxValue, maxValueDataUnits] = this.convertUnits(Math.max(maxValueData)).split(' ');
+ maxValue = maxValueData;
}
-
- this.chart.chart.options.scales.yAxes[0].ticks.suggestedMax = maxValue * 1.2;
- this.chart.chart.options.scales.yAxes[0].ticks.suggestedMin = 0;
- this.chart.chart.options.scales.yAxes[0].ticks.stepSize = Number(
- ((maxValue * 1.2) / 2).toFixed(0)
- );
- this.chart.chart.options.scales.yAxes[0].ticks.callback = (value: any) => {
- if (value === 0) {
- return null;
- }
- if (!maxValueDataUnits) {
- return this.fillString(`${value}`);
- }
- return this.fillString(`${value} ${maxValueDataUnits}`);
- };
- this.chart.chart.update();
+ [maxValue, maxValueDataUnits] = this.convertUnits(maxValue).split(' ');
}
+
+ const yAxesTicks = this.chart.chart.options.scales.yAxes[0].ticks;
+ yAxesTicks.suggestedMax = maxValue * extraRoom;
+ yAxesTicks.suggestedMin = 0;
+ yAxesTicks.stepSize = Number((yAxesTicks.suggestedMax / 2).toFixed(0));
+ yAxesTicks.callback = (value: any) => {
+ if (value === 0) {
+ return null;
+ }
+ if (!maxValueDataUnits) {
+ return this.fillString(`${value}`);
+ }
+ return this.fillString(`${value} ${maxValueDataUnits}`);
+ };
+ this.chartDataUnits = maxValueDataUnits || '';
+ this.chart.chart.update();
}
}
<ng-container *ngIf="capacity">
<cd-dashboard-area-chart chartTitle="Used Capacity"
[maxValue]="capacity.total_bytes"
- dataUnits="bytes"
+ dataUnits="B"
label="Used Capacity"
[data]="queriesResults.USEDCAPACITY">
</cd-dashboard-area-chart>
</ng-container>
<cd-dashboard-area-chart chartTitle="IOPS"
- dataUnits="none"
+ dataUnits=""
label="OPS"
label2="IPS"
[data]="queriesResults.OPS"
[data2]="queriesResults.WRITELATENCY">
</cd-dashboard-area-chart>
<cd-dashboard-area-chart chartTitle="Client Throughput"
- dataUnits="bytesPerSecond"
+ dataUnits="B/s"
label="Read"
label2="Write"
[data]="queriesResults.READCLIENTTHROUGHPUT"
[data2]="queriesResults.WRITECLIENTTHROUGHPUT">
</cd-dashboard-area-chart>
<cd-dashboard-area-chart chartTitle="Recovery Throughput"
- dataUnits="bytesPerSecond"
+ dataUnits="B/s"
label="Recovery Throughput"
[data]="queriesResults.RECOVERYBYTES">
</cd-dashboard-area-chart>
transform(value: any): any {
return this.formatter.format_number(value, 1024, [
'B/s',
- 'kB/s',
- 'MB/s',
- 'GB/s',
- 'TB/s',
- 'PB/s',
- 'EB/s',
- 'ZB/s',
- 'YB/s'
+ 'KiB/s',
+ 'MiB/s',
+ 'GiB/s',
+ 'TiB/s',
+ 'PiB/s',
+ 'EiB/s',
+ 'ZiB/s',
+ 'YiB/s'
]);
}
}
});
});
+ describe('formatNumberFromTo', () => {
+ const formats = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
+ const formats2 = ['ns', 'μs', 'ms', 's'];
+
+ it('should test some values and data units', () => {
+ expect(service.formatNumberFromTo('0.1', 'B', 'TiB', 1024, formats)).toBe('0 TiB');
+ expect(service.formatNumberFromTo('1024', 'B', 'KiB', 1024, formats)).toBe('1 KiB');
+ expect(service.formatNumberFromTo(1000, 'mib', 'gib', 1024, formats, 3)).toBe('0.977 gib');
+ expect(service.formatNumberFromTo(1024, 'GiB', 'MiB', 1024, formats)).toBe('1048576 MiB');
+ expect(
+ service.formatNumberFromTo(23.45678 * Math.pow(1024, 3), 'B', 'GiB', 1024, formats)
+ ).toBe('23.5 GiB');
+ expect(
+ service.formatNumberFromTo(23.45678 * Math.pow(1024, 3), 'B', 'GiB', 1024, formats, 2)
+ ).toBe('23.46 GiB');
+
+ expect(service.formatNumberFromTo('128', 'ns', 'ms', 1000, formats2)).toBe('0 ms');
+ expect(service.formatNumberFromTo(128, 'ns', 'ms', 1000, formats2, 4)).toBe('0.0001 ms');
+ expect(service.formatNumberFromTo(20, 's', 'ms', 1000, formats2, 4)).toBe('20000 ms');
+ });
+ });
+
describe('toBytes', () => {
it('should not convert wrong values', () => {
expect(service.toBytes('10xyz')).toBeNull();
return result;
}
+ /**
+ * Converts a value from one set of units to another using a conversion factor
+ * @param n The value to be converted
+ * @param units The data units of the value
+ * @param targetedUnits The wanted data units to convert to
+ * @param conversionFactor The factor of convesion
+ * @param unitsArray An ordered array containing the data units
+ * @param decimals The number of decimals on the returned value
+ * @returns Returns a string of the given value formated to the targeted data units.
+ */
+ formatNumberFromTo(
+ n: any,
+ units: any,
+ targetedUnits: string,
+ conversionFactor: number,
+ unitsArray: string[],
+ decimals: number = 1
+ ): string {
+ if (_.isString(n)) {
+ n = Number(n);
+ }
+ if (!_.isNumber(n)) {
+ return '-';
+ }
+ const unitsArrayLowerCase = unitsArray.map((str) => str.toLowerCase());
+ if (
+ !unitsArrayLowerCase.includes(units.toLowerCase()) ||
+ !unitsArrayLowerCase.includes(targetedUnits.toLowerCase())
+ ) {
+ return `${n} ${units}`;
+ }
+ const index =
+ unitsArrayLowerCase.indexOf(units.toLowerCase()) -
+ unitsArrayLowerCase.indexOf(targetedUnits.toLocaleLowerCase());
+ const convertedN =
+ index > 0
+ ? n * Math.pow(conversionFactor, index)
+ : n / Math.pow(conversionFactor, Math.abs(index));
+ let result = _.round(convertedN, decimals).toString();
+ result = `${result} ${targetedUnits}`;
+ return result;
+ }
+
/**
* Convert the given value into bytes.
* @param {string} value The value to be converted, e.g. 1024B, 10M, 300KiB or 1ZB.
--- /dev/null
+import { TestBed } from '@angular/core/testing';
+
+import { NumberFormatterService } from './number-formatter.service';
+
+describe('FormatToService', () => {
+ let service: NumberFormatterService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(NumberFormatterService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
--- /dev/null
+import { Injectable } from '@angular/core';
+import { FormatterService } from './formatter.service';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NumberFormatterService {
+ readonly bytesLabels = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
+ readonly bytesPerSecondLabels = [
+ 'B/s',
+ 'KiB/s',
+ 'MiB/s',
+ 'GiB/s',
+ 'TiB/s',
+ 'PiB/s',
+ 'EiB/s',
+ 'ZiB/s',
+ 'YiB/s'
+ ];
+ readonly secondsLabels = ['ns', 'μs', 'ms', 's', 'ks', 'Ms'];
+ readonly unitlessLabels = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
+
+ constructor(private formatter: FormatterService) {}
+
+ formatFromTo(
+ value: any,
+ units: string,
+ targetedUnits: string,
+ factor: number,
+ labels: string[]
+ ): any {
+ return this.formatter.formatNumberFromTo(value, units, targetedUnits, factor, labels);
+ }
+
+ formatBytesFromTo(value: any, units: string, targetedUnits: string): any {
+ return this.formatFromTo(value, units, targetedUnits, 1024, this.bytesLabels);
+ }
+
+ formatBytesPerSecondFromTo(value: any, units: string, targetedUnits: string): any {
+ return this.formatFromTo(value, units, targetedUnits, 1024, this.bytesPerSecondLabels);
+ }
+
+ formatSecondsFromTo(value: any, units: string, targetedUnits: string): any {
+ return this.formatFromTo(value, units, targetedUnits, 1000, this.secondsLabels);
+ }
+
+ formatUnitlessFromTo(value: any, units: string, targetedUnits: string): any {
+ return this.formatFromTo(value, units, targetedUnits, 1000, this.unitlessLabels);
+ }
+}