]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
e06746bf9b1feb3ce06bf02a8d94911e09088d0c
[ceph-ci.git] /
1 import { AfterViewInit, Component, Input, OnChanges, ViewChild } from '@angular/core';
2
3 import { CssHelper } from '~/app/shared/classes/css-helper';
4 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
5 import { DimlessBinaryPerSecondPipe } from '~/app/shared/pipes/dimless-binary-per-second.pipe';
6 import { FormatterService } from '~/app/shared/services/formatter.service';
7 import { BaseChartDirective, PluginServiceGlobalRegistrationAndOptions } from 'ng2-charts';
8 import { DimlessPipe } from '~/app/shared/pipes/dimless.pipe';
9 import { NumberFormatterService } from '~/app/shared/services/number-formatter.service';
10
11 @Component({
12   selector: 'cd-dashboard-area-chart',
13   templateUrl: './dashboard-area-chart.component.html',
14   styleUrls: ['./dashboard-area-chart.component.scss']
15 })
16 export class DashboardAreaChartComponent implements OnChanges, AfterViewInit {
17   @ViewChild(BaseChartDirective) chart: BaseChartDirective;
18
19   @Input()
20   chartTitle: string;
21   @Input()
22   maxValue?: number;
23   @Input()
24   dataUnits: string;
25   @Input()
26   data: Array<[number, string]>;
27   @Input()
28   data2?: Array<[number, string]>;
29   @Input()
30   label: string;
31   @Input()
32   label2?: string;
33   @Input()
34   decimals?: number = 1;
35
36   currentDataUnits: string;
37   currentData: number;
38   currentDataUnits2?: string;
39   currentData2?: number;
40
41   chartDataUnits: string;
42
43   chartData: any = {
44     dataset: [
45       {
46         label: '',
47         data: [{ x: 0, y: 0 }],
48         tension: 0,
49         pointBackgroundColor: this.cssHelper.propertyValue('chart-color-strong-blue'),
50         backgroundColor: this.cssHelper.propertyValue('chart-color-translucent-blue'),
51         borderColor: this.cssHelper.propertyValue('chart-color-strong-blue')
52       },
53       {
54         label: '',
55         data: [],
56         tension: 0,
57         pointBackgroundColor: this.cssHelper.propertyValue('chart-color-orange'),
58         backgroundColor: this.cssHelper.propertyValue('chart-color-yellow'),
59         borderColor: this.cssHelper.propertyValue('chart-color-orange')
60       }
61     ]
62   };
63
64   options: any = {
65     responsive: true,
66     maintainAspectRatio: false,
67     elements: {
68       point: {
69         radius: 0
70       }
71     },
72     legend: {
73       display: false
74     },
75     tooltips: {
76       intersect: false,
77       displayColors: true,
78       backgroundColor: this.cssHelper.propertyValue('chart-color-tooltip-background'),
79       callbacks: {
80         title: function (tooltipItem: any): any {
81           return tooltipItem[0].xLabel;
82         }
83       }
84     },
85     hover: {
86       intersect: false
87     },
88     scales: {
89       xAxes: [
90         {
91           display: false,
92           type: 'time',
93           gridLines: {
94             display: false
95           },
96           time: {
97             tooltipFormat: 'YYYY/MM/DD hh:mm:ss'
98           }
99         }
100       ],
101       yAxes: [
102         {
103           gridLines: {
104             display: false
105           },
106           ticks: {
107             beginAtZero: true,
108             maxTicksLimit: 3,
109             callback: (value: any) => {
110               if (value === 0) {
111                 return null;
112               }
113               return this.fillString(this.convertUnits(value));
114             }
115           }
116         }
117       ]
118     },
119     plugins: {
120       borderArea: true,
121       chartAreaBorder: {
122         borderColor: this.cssHelper.propertyValue('chart-color-slight-dark-gray'),
123         borderWidth: 2
124       }
125     }
126   };
127
128   public chartAreaBorderPlugin: PluginServiceGlobalRegistrationAndOptions[] = [
129     {
130       beforeDraw(chart: Chart) {
131         if (!chart.options.plugins.borderArea) {
132           return;
133         }
134         const {
135           ctx,
136           chartArea: { left, top, right, bottom }
137         } = chart;
138         ctx.save();
139         ctx.strokeStyle = chart.options.plugins.chartAreaBorder.borderColor;
140         ctx.lineWidth = chart.options.plugins.chartAreaBorder.borderWidth;
141         ctx.setLineDash(chart.options.plugins.chartAreaBorder.borderDash || []);
142         ctx.lineDashOffset = chart.options.plugins.chartAreaBorder.borderDashOffset;
143         ctx.strokeRect(left, top, right - left - 1, bottom);
144         ctx.restore();
145       }
146     }
147   ];
148
149   constructor(
150     private cssHelper: CssHelper,
151     private dimlessBinary: DimlessBinaryPipe,
152     private dimlessBinaryPerSecond: DimlessBinaryPerSecondPipe,
153     private dimlessPipe: DimlessPipe,
154     private formatter: FormatterService,
155     private numberFormatter: NumberFormatterService
156   ) {}
157
158   ngOnChanges(): void {
159     this.updateChartData();
160   }
161
162   ngAfterViewInit(): void {
163     this.updateChartData();
164   }
165
166   private updateChartData(): void {
167     if (this.data) {
168       this.setChartTicks();
169       this.chartData.dataset[0].data = this.formatData(this.data);
170       this.chartData.dataset[0].label = this.label;
171       [this.currentData, this.currentDataUnits] = this.convertUnits(
172         this.data[this.data.length - 1][1]
173       ).split(' ');
174     }
175     if (this.data2) {
176       this.chartData.dataset[1].data = this.formatData(this.data2);
177       this.chartData.dataset[1].label = this.label2;
178       [this.currentData2, this.currentDataUnits2] = this.convertUnits(
179         this.data2[this.data2.length - 1][1]
180       ).split(' ');
181     }
182     if (this.chart) {
183       this.chart.chart.update();
184     }
185   }
186
187   private formatData(array: Array<any>): any {
188     let formattedData = {};
189     formattedData = array.map((data: any) => ({
190       x: data[0] * 1000,
191       y: Number(this.convertToChartDataUnits(data[1]).replace(/[^\d,.]+/g, ''))
192     }));
193     return formattedData;
194   }
195
196   private convertToChartDataUnits(data: any): any {
197     let dataWithUnits: string = '';
198     if (this.chartDataUnits) {
199       if (this.dataUnits === 'B') {
200         dataWithUnits = this.numberFormatter.formatBytesFromTo(
201           data,
202           this.dataUnits,
203           this.chartDataUnits
204         );
205       } else if (this.dataUnits === 'B/s') {
206         dataWithUnits = this.numberFormatter.formatBytesPerSecondFromTo(
207           data,
208           this.dataUnits,
209           this.chartDataUnits
210         );
211       } else if (this.dataUnits === 'ms') {
212         dataWithUnits = this.numberFormatter.formatSecondsFromTo(
213           data,
214           this.dataUnits,
215           this.chartDataUnits,
216           this.decimals
217         );
218       } else {
219         dataWithUnits = this.numberFormatter.formatUnitlessFromTo(
220           data,
221           this.dataUnits,
222           this.chartDataUnits
223         );
224       }
225     }
226     return dataWithUnits;
227   }
228
229   private convertUnits(data: any): any {
230     let dataWithUnits: string = '';
231     if (this.dataUnits === 'B') {
232       dataWithUnits = this.dimlessBinary.transform(data);
233     } else if (this.dataUnits === 'B/s') {
234       dataWithUnits = this.dimlessBinaryPerSecond.transform(data);
235     } else if (this.dataUnits === 'ms') {
236       dataWithUnits = this.formatter.format_number(data, 1000, ['ms', 's'], this.decimals);
237     } else {
238       dataWithUnits = this.dimlessPipe.transform(data);
239     }
240     return dataWithUnits;
241   }
242
243   private fillString(str: string): string {
244     let maxNumberOfChar: number = 8;
245     let numberOfChars: number = str.length;
246     if (str.length < 4) {
247       maxNumberOfChar = 11;
248     }
249     for (; numberOfChars < maxNumberOfChar; numberOfChars++) {
250       str = '\u00A0' + str;
251     }
252     return str + '\u00A0\u00A0';
253   }
254
255   private setChartTicks() {
256     if (!this.chart) {
257       return;
258     }
259
260     let maxValue = 0;
261     let maxValueDataUnits = '';
262     let extraRoom = 1.2;
263
264     if (this.maxValue) {
265       extraRoom = 1.0;
266       [maxValue, maxValueDataUnits] = this.convertUnits(this.maxValue).split(' ');
267     } else if (this.data) {
268       extraRoom = 1.2;
269       let maxValueData = Math.max(...this.data.map((values: any) => values[1]));
270       if (this.data2) {
271         let maxValueData2 = Math.max(...this.data2.map((values: any) => values[1]));
272         maxValue = Math.max(maxValueData, maxValueData2);
273       } else {
274         maxValue = maxValueData;
275       }
276       [maxValue, maxValueDataUnits] = this.convertUnits(maxValue).split(' ');
277     }
278
279     const yAxesTicks = this.chart.chart.options.scales.yAxes[0].ticks;
280     yAxesTicks.suggestedMax = maxValue * extraRoom;
281     yAxesTicks.suggestedMin = 0;
282     yAxesTicks.stepSize = Number((yAxesTicks.suggestedMax / 2).toFixed(0));
283     yAxesTicks.callback = (value: any) => {
284       if (value === 0) {
285         return null;
286       }
287       if (!maxValueDataUnits) {
288         return this.fillString(`${value}`);
289       }
290       return this.fillString(`${value} ${maxValueDataUnits}`);
291     };
292     this.chartDataUnits = maxValueDataUnits || '';
293     this.chart.chart.update();
294   }
295 }