1 /* Copyright (c) 2019 AT&T Intellectual Property. #
\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
7 # http://www.apache.org/licenses/LICENSE-2.0 #
\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
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
23 //am4core.useTheme(am4themes_animated);
\r
26 selector: 'app-multi-line-chart',
\r
27 templateUrl: './multi-line-chart.component.pug',
\r
28 styleUrls: ['./multi-line-chart.component.scss']
\r
30 export class MultiLineChartComponent implements OnInit {
\r
32 @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;
\r
33 @Input() height: string;
\r
35 public chart: am4charts.XYChart;
\r
36 public loadingIndicator;
\r
38 protected stats: StatsService;
\r
39 public dataIsEmpty = 0;
\r
40 constructor(private statsService: StatsService) {
\r
41 this.stats = statsService;
\r
46 this.stats.onDefaultDataCallStarted().subscribe(res => {
\r
47 this.showLoadingIndicator();
\r
50 this.stats.onTIExecutionChangeStarted().subscribe(res => {
\r
51 this.showLoadingIndicator();
\r
54 this.stats.onDefaultDataCallFinished().subscribe(res => {
\r
56 this.hideLoadingIndicator();
\r
59 this.stats.onTIExecutionChangeFinished().subscribe(res => {
\r
61 this.hideLoadingIndicator();
\r
65 //Resize if screen size changes.
\r
66 // this.stats.checkWindow().subscribe(res=>{
\r
67 // this.renderChart();
\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
76 //Names of the test instances.
\r
77 var InstanceStrings = {};
\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
85 // Iterate through the test instances.
\r
86 for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {
\r
88 //For each instance go through the dates.
\r
89 for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {
\r
91 //Check newData to see if date has been pushed.
\r
92 var dateIndex = newData.findIndex((element) => {
\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
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
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
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
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
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
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
129 let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);
\r
133 this.stats.filters.startDate = (moment().subtract(2, "months").toDate());
\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
142 testInstancename: "empty"
\r
145 resolve(formattedData);
\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
162 //Go through the data
\r
163 for (let i = 0; i < rawData.length; i++) {
\r
165 //for the first iteration,
\r
167 formattedData.push(rawData[0]);
\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
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
179 //push the new date and all the strings for the test instances.
\r
180 objectToAdd = Object.assign(objectToAdd, InstanceStrings);
\r
182 //add date to the beginning of the array.
\r
183 formattedData.unshift(objectToAdd)
\r
188 //for all other iterations
\r
191 //get the difference in days.
\r
192 let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);
\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
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
205 formattedData.push(rawData[i]);
\r
209 if (rawData.length >= 1) {
\r
210 var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);
\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
217 objectToAdd = Object.assign(objectToAdd, InstanceStrings);
\r
218 formattedData.push(objectToAdd);
\r
224 resolve(formattedData);
\r
228 daysDuration(date1, date2) {
\r
229 return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));
\r
232 addDaysToDate(date, days) {
\r
233 let newDate = new Date(date);
\r
234 newDate.setDate(date.getDate() + days);
\r
238 //initialize loading indicator
\r
239 showLoadingIndicator() {
\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
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
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
268 hideLoadingIndicator() {
\r
269 this.loadingIndicator.hide();
\r
272 async renderChart() {
\r
273 //console.log("here")
\r
275 am4core.options.minPolylineStep = 5;
\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
280 this.chart.paddingRight = 20;
\r
281 this.chartData = this.stats.getData("multiLineData");
\r
283 //reformat the data to match the format needed for amcharts.
\r
284 var formattedData = this.reformatData();
\r
287 formattedData.sort((a, b) => a.date - b.date);
\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
294 this.chart.data = formattedData;
\r
296 let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());
\r
297 dateAxis.title.text = "Date";
\r
299 let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());
\r
300 valueAxis.title.text = "Executions";
\r
302 this.chart.cursor = new am4charts.XYCursor();
\r
303 this.chart.cursor.xAxis = dateAxis;
\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
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
331 this.chart.legend = new am4charts.Legend();
\r
332 this.chart.legend.labels.template.text = "[bold {color}]{name}";
\r