added svcapi ui and camunda code
[it/otf.git] / otf-frontend / client / src / app / layout / components / stats / multi-line-chart / multi-line-chart.component.ts
1 /*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
2 #                                                                              #\r
3 #   Licensed under the Apache License, Version 2.0 (the "License");            #\r
4 #   you may not use this file except in compliance with the License.           #\r
5 #   You may obtain a copy of the License at                                    #\r
6 #                                                                              #\r
7 #       http://www.apache.org/licenses/LICENSE-2.0                             #\r
8 #                                                                              #\r
9 #   Unless required by applicable law or agreed to in writing, software        #\r
10 #   distributed under the License is distributed on an "AS IS" BASIS,          #\r
11 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
12 #   See the License for the specific language governing permissions and        #\r
13 #   limitations under the License.                                             #\r
14 ##############################################################################*/\r
15 \r
16 \r
17 import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';\r
18 import { StatsService } from '../stats.service';\r
19 import * as am4core from "@amcharts/amcharts4/core";\r
20 import * as am4charts from "@amcharts/amcharts4/charts";\r
21 import * as moment from 'moment';\r
22 \r
23 //am4core.useTheme(am4themes_animated);\r
24 \r
25 @Component({\r
26   selector: 'app-multi-line-chart',\r
27   templateUrl: './multi-line-chart.component.pug',\r
28   styleUrls: ['./multi-line-chart.component.scss']\r
29 })\r
30 export class MultiLineChartComponent implements OnInit {\r
31 \r
32   @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;\r
33   @Input() height: string;\r
34 \r
35   public chart: am4charts.XYChart;\r
36   public loadingIndicator;\r
37   public chartData;\r
38   protected stats: StatsService;\r
39   public dataIsEmpty = 0;\r
40   constructor(private statsService: StatsService) {\r
41     this.stats = statsService;\r
42   }\r
43 \r
44   ngOnInit() {\r
45 \r
46     this.stats.onDefaultDataCallStarted().subscribe(res => {\r
47       this.showLoadingIndicator();\r
48     })\r
49 \r
50     this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
51       this.showLoadingIndicator();\r
52     })\r
53 \r
54     this.stats.onDefaultDataCallFinished().subscribe(res => {\r
55       this.renderChart();\r
56       this.hideLoadingIndicator();\r
57     })\r
58 \r
59     this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
60       this.renderChart();\r
61       this.hideLoadingIndicator();\r
62     })\r
63     this.renderChart();\r
64 \r
65     //Resize if screen size changes.\r
66     // this.stats.checkWindow().subscribe(res=>{\r
67     //   this.renderChart();\r
68     // })\r
69   }\r
70 \r
71 \r
72   // Rearrange the data to match the format needed for amcharts. Need to group by date. Each object has a date and a list of the lines and its count.\r
73   reformatData() {\r
74     var newData = [];\r
75 \r
76     //Names of the test instances. \r
77     var InstanceStrings = {};\r
78 \r
79     //Go through the instances and add the names to the InstanceStrings Array. \r
80     for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
81       var instanceName = this.chartData[numberInstances].testInstanceName;\r
82       InstanceStrings[instanceName] = 0;\r
83     }\r
84 \r
85     // Iterate through the test instances. \r
86     for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {\r
87 \r
88       //For each instance go through the dates. \r
89       for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {\r
90 \r
91         //Check newData to see if date has been pushed. \r
92         var dateIndex = newData.findIndex((element) => {\r
93           return (\r
94             this.chartData[instanceLength].dateData[numDates].date.getFullYear() === element.date.getFullYear() &&\r
95             this.chartData[instanceLength].dateData[numDates].date.getMonth() === element.date.getMonth() &&\r
96             this.chartData[instanceLength].dateData[numDates].date.getDate() === element.date.getDate()\r
97           )\r
98         });\r
99 \r
100         //If date is not present push the new date and the count for the test instance. \r
101         if (newData[dateIndex] == undefined) {\r
102           //date is not present\r
103           var newDate = {\r
104             date: new Date(this.chartData[instanceLength].dateData[numDates].date.getFullYear(),\r
105               this.chartData[instanceLength].dateData[numDates].date.getMonth(),\r
106               this.chartData[instanceLength].dateData[numDates].date.getDate())\r
107           };\r
108           newDate = Object.assign(newDate, InstanceStrings);\r
109           newDate[this.chartData[instanceLength].testInstanceName] = this.chartData[instanceLength].dateData[numDates].count;\r
110           newData.push(newDate);\r
111         } else {\r
112 \r
113           //If the date is present update the count for that test instance. \r
114           newData[dateIndex][this.chartData[instanceLength].testInstanceName] += this.chartData[instanceLength].dateData[numDates].count;\r
115         }\r
116       }\r
117     }\r
118     return newData;\r
119   }\r
120 \r
121   //fill in dates that have no data. If a specific date is not present, we need to fill in the date and set a count for 0. \r
122   async setupPoints(rawData): Promise<any> {\r
123     let formattedData = [];\r
124 \r
125     //If the chart is supposed to be empty push in a line with a count of 0.\r
126     if (rawData.length == 0) {\r
127       return new Promise((resolve, reject) => {\r
128 \r
129         let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);\r
130 \r
131         if (!days) {\r
132           days = 62;\r
133           this.stats.filters.startDate = (moment().subtract(2, "months").toDate());\r
134         }\r
135 \r
136         // Go through 62 days and push a count of 0/\r
137         for (let day = 0; day < days; day++) {\r
138           let newDate = this.addDaysToDate(this.stats.filters.startDate, day);\r
139           formattedData.push({\r
140             date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
141             count: 0,\r
142             testInstancename: "empty"\r
143           })\r
144         }\r
145         resolve(formattedData);\r
146       })\r
147 \r
148 \r
149 \r
150 \r
151 \r
152       //Data is not empty. push in empty days for each instance. Some instances might not have executions for each day. \r
153     } else return new Promise((resolve, reject) => {\r
154       //get list of test instances. \r
155       var InstanceStrings = {};\r
156       for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
157         var instanceName = this.chartData[numberInstances].testInstanceName;\r
158         InstanceStrings[instanceName] = 0;\r
159       }\r
160 \r
161 \r
162       //Go through the data\r
163       for (let i = 0; i < rawData.length; i++) {\r
164 \r
165         //for the first iteration, \r
166         if (i == 0) {\r
167           formattedData.push(rawData[0]);\r
168 \r
169           // if the date is before the startDate specified by the filter or two months be default. \r
170           if (formattedData[0].date > this.stats.filters.startDate) {\r
171 \r
172             // Go through the difference in days and push the date and count of 0. \r
173             let days = this.daysDuration(this.stats.filters.startDate, formattedData[0].date)\r
174             for (let k = days - 1; k >= 0; k--) {\r
175               let newDate = this.addDaysToDate(this.stats.filters.startDate, k);\r
176               var objectToAdd = {\r
177                 date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
178               };\r
179               //push the new date and all the strings for the test instances.\r
180               objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
181 \r
182               //add date to the beginning of the array. \r
183               formattedData.unshift(objectToAdd)\r
184 \r
185             }\r
186           }\r
187 \r
188           //for all other iterations\r
189         } else {\r
190 \r
191           //get the difference in days.\r
192           let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);\r
193           if (days > 1) {\r
194             //push the new dates. \r
195             for (let j = 1; j < days; j++) {\r
196               let newDate = this.addDaysToDate(rawData[i - 1].date, j);\r
197               var objectToAdd = {\r
198                 date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
199               };\r
200               //push the new date and all the strings for the test instances.\r
201               objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
202               formattedData.push(objectToAdd);\r
203             }\r
204           }\r
205           formattedData.push(rawData[i]);\r
206         }\r
207       }\r
208 \r
209       if (rawData.length >= 1) {\r
210         var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);\r
211         if (days >= 1) {\r
212           for (let j = 1; j < days; j++) {\r
213             let newDate = this.addDaysToDate(rawData[rawData.length - 1].date, j);\r
214             var objectToAdd = {\r
215               date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
216             };\r
217             objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
218             formattedData.push(objectToAdd);\r
219           }\r
220         }\r
221       }\r
222 \r
223 \r
224       resolve(formattedData);\r
225     })\r
226   }\r
227 \r
228   daysDuration(date1, date2) {\r
229     return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));\r
230   }\r
231 \r
232   addDaysToDate(date, days) {\r
233     let newDate = new Date(date);\r
234     newDate.setDate(date.getDate() + days);\r
235     return newDate;\r
236   }\r
237 \r
238   //initialize loading indicator\r
239   showLoadingIndicator() {\r
240 \r
241     this.height = "380px";\r
242     this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
243     this.loadingIndicator.background.fill = am4core.color("#fff");\r
244     this.loadingIndicator.background.fillOpacity = 0.8;\r
245     this.loadingIndicator.width = am4core.percent(100);\r
246     this.loadingIndicator.height = am4core.percent(100);\r
247 \r
248     let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
249     indicatorLabel.text = "Loading..";\r
250     indicatorLabel.align = "center";\r
251     indicatorLabel.valign = "middle";\r
252     indicatorLabel.fontSize = 18;\r
253     indicatorLabel.fontWeight= "bold";\r
254     indicatorLabel.dy = 50;\r
255 \r
256     let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
257     //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
258     loadingImage.href = "https://loading.io/spinners/equalizer/lg.equalizer-bars-loader.gif";\r
259     //loadingImage.dataSource = "/loading-pies.svg"\r
260     loadingImage.align = "center";\r
261     loadingImage.valign = "middle";\r
262     loadingImage.horizontalCenter = "middle";\r
263     loadingImage.verticalCenter = "middle";\r
264     loadingImage.scale = 3.0;\r
265 \r
266   }\r
267 \r
268   hideLoadingIndicator() {\r
269     this.loadingIndicator.hide();\r
270   }\r
271 \r
272   async renderChart() {\r
273     //console.log("here")\r
274 \r
275     am4core.options.minPolylineStep = 5;\r
276 \r
277     this.chart = am4core.create(this.MultiLineChartDiv.nativeElement, am4charts.XYChart);\r
278     this.chart.hiddenState.properties.opacity = 0; // this creates initial fade-in\r
279 \r
280     this.chart.paddingRight = 20;\r
281     this.chartData = this.stats.getData("multiLineData");\r
282 \r
283     //reformat the data to match the format needed for amcharts. \r
284     var formattedData = this.reformatData();\r
285 \r
286     //sort the data.\r
287     formattedData.sort((a, b) => a.date - b.date);\r
288 \r
289     //fill in gaps in the data\r
290     await this.setupPoints(formattedData).then(res => {\r
291       formattedData = res;\r
292     }, err => console.log(err));\r
293 \r
294     this.chart.data = formattedData;\r
295 \r
296     let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
297     dateAxis.title.text = "Date";\r
298 \r
299     let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
300     valueAxis.title.text = "Executions";\r
301 \r
302     this.chart.cursor = new am4charts.XYCursor();\r
303     this.chart.cursor.xAxis = dateAxis;\r
304 \r
305     //if the data is empty, push in a line and set the count to 0. \r
306     if (this.chartData.length == 0) {\r
307       this.chartData.push({ testInstanceName: "empty" })\r
308       let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
309       newSeries.name = "empty";\r
310       newSeries.dataFields.dateX = "date";\r
311       newSeries.dataFields.valueY = "count";\r
312       newSeries.tooltipText = "{valueY.value}";\r
313       newSeries.sequencedInterpolation = true;\r
314       newSeries.defaultState.transitionDuration = 1000;\r
315       newSeries.strokeWidth = 3;\r
316       newSeries.tensionX = 0.8;\r
317     } else {\r
318 \r
319       //initialize the lines for the series\r
320       for (let index = 0; index < this.chartData.length; index++) {\r
321         let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
322         newSeries.name = this.chartData[index].testInstanceName;\r
323         newSeries.dataFields.dateX = "date";\r
324         newSeries.dataFields.valueY = (this.chartData[index].testInstanceName).toString();\r
325         newSeries.tooltipText = "{valueY.value}";\r
326         newSeries.sequencedInterpolation = true;\r
327         newSeries.defaultState.transitionDuration = 1000;\r
328         newSeries.strokeWidth = 3;\r
329         newSeries.tensionX = 0.8;\r
330       }\r
331       this.chart.legend = new am4charts.Legend();\r
332       this.chart.legend.labels.template.text = "[bold {color}]{name}";\r
333     }\r
334   }\r
335 \r
336 }