--- /dev/null
+/* Copyright (c) 2019 AT&T Intellectual Property. #\r
+# #\r
+# Licensed under the Apache License, Version 2.0 (the "License"); #\r
+# you may not use this file except in compliance with the License. #\r
+# You may obtain a copy of the License at #\r
+# #\r
+# http://www.apache.org/licenses/LICENSE-2.0 #\r
+# #\r
+# Unless required by applicable law or agreed to in writing, software #\r
+# distributed under the License is distributed on an "AS IS" BASIS, #\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #\r
+# See the License for the specific language governing permissions and #\r
+# limitations under the License. #\r
+##############################################################################*/\r
+\r
+\r
+import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';\r
+import { StatsService } from '../stats.service';\r
+import * as am4core from "@amcharts/amcharts4/core";\r
+import * as am4charts from "@amcharts/amcharts4/charts";\r
+import * as moment from 'moment';\r
+\r
+//am4core.useTheme(am4themes_animated);\r
+\r
+@Component({\r
+ selector: 'app-multi-line-chart',\r
+ templateUrl: './multi-line-chart.component.pug',\r
+ styleUrls: ['./multi-line-chart.component.scss']\r
+})\r
+export class MultiLineChartComponent implements OnInit {\r
+\r
+ @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;\r
+ @Input() height: string;\r
+\r
+ public chart: am4charts.XYChart;\r
+ public loadingIndicator;\r
+ public chartData;\r
+ protected stats: StatsService;\r
+ public dataIsEmpty = 0;\r
+ constructor(private statsService: StatsService) {\r
+ this.stats = statsService;\r
+ }\r
+\r
+ ngOnInit() {\r
+\r
+ this.stats.onDefaultDataCallStarted().subscribe(res => {\r
+ this.showLoadingIndicator();\r
+ })\r
+\r
+ this.stats.onTIExecutionChangeStarted().subscribe(res => {\r
+ this.showLoadingIndicator();\r
+ })\r
+\r
+ this.stats.onDefaultDataCallFinished().subscribe(res => {\r
+ this.renderChart();\r
+ this.hideLoadingIndicator();\r
+ })\r
+\r
+ this.stats.onTIExecutionChangeFinished().subscribe(res => {\r
+ this.renderChart();\r
+ this.hideLoadingIndicator();\r
+ })\r
+ this.renderChart();\r
+\r
+ //Resize if screen size changes.\r
+ // this.stats.checkWindow().subscribe(res=>{\r
+ // this.renderChart();\r
+ // })\r
+ }\r
+\r
+\r
+ // 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
+ reformatData() {\r
+ var newData = [];\r
+\r
+ //Names of the test instances. \r
+ var InstanceStrings = {};\r
+\r
+ //Go through the instances and add the names to the InstanceStrings Array. \r
+ for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+ var instanceName = this.chartData[numberInstances].testInstanceName;\r
+ InstanceStrings[instanceName] = 0;\r
+ }\r
+\r
+ // Iterate through the test instances. \r
+ for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {\r
+\r
+ //For each instance go through the dates. \r
+ for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {\r
+\r
+ //Check newData to see if date has been pushed. \r
+ var dateIndex = newData.findIndex((element) => {\r
+ return (\r
+ this.chartData[instanceLength].dateData[numDates].date.getFullYear() === element.date.getFullYear() &&\r
+ this.chartData[instanceLength].dateData[numDates].date.getMonth() === element.date.getMonth() &&\r
+ this.chartData[instanceLength].dateData[numDates].date.getDate() === element.date.getDate()\r
+ )\r
+ });\r
+\r
+ //If date is not present push the new date and the count for the test instance. \r
+ if (newData[dateIndex] == undefined) {\r
+ //date is not present\r
+ var newDate = {\r
+ date: new Date(this.chartData[instanceLength].dateData[numDates].date.getFullYear(),\r
+ this.chartData[instanceLength].dateData[numDates].date.getMonth(),\r
+ this.chartData[instanceLength].dateData[numDates].date.getDate())\r
+ };\r
+ newDate = Object.assign(newDate, InstanceStrings);\r
+ newDate[this.chartData[instanceLength].testInstanceName] = this.chartData[instanceLength].dateData[numDates].count;\r
+ newData.push(newDate);\r
+ } else {\r
+\r
+ //If the date is present update the count for that test instance. \r
+ newData[dateIndex][this.chartData[instanceLength].testInstanceName] += this.chartData[instanceLength].dateData[numDates].count;\r
+ }\r
+ }\r
+ }\r
+ return newData;\r
+ }\r
+\r
+ //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
+ async setupPoints(rawData): Promise<any> {\r
+ let formattedData = [];\r
+\r
+ //If the chart is supposed to be empty push in a line with a count of 0.\r
+ if (rawData.length == 0) {\r
+ return new Promise((resolve, reject) => {\r
+\r
+ let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);\r
+\r
+ if (!days) {\r
+ days = 62;\r
+ this.stats.filters.startDate = (moment().subtract(2, "months").toDate());\r
+ }\r
+\r
+ // Go through 62 days and push a count of 0/\r
+ for (let day = 0; day < days; day++) {\r
+ let newDate = this.addDaysToDate(this.stats.filters.startDate, day);\r
+ formattedData.push({\r
+ date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+ count: 0,\r
+ testInstancename: "empty"\r
+ })\r
+ }\r
+ resolve(formattedData);\r
+ })\r
+\r
+\r
+\r
+\r
+\r
+ //Data is not empty. push in empty days for each instance. Some instances might not have executions for each day. \r
+ } else return new Promise((resolve, reject) => {\r
+ //get list of test instances. \r
+ var InstanceStrings = {};\r
+ for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {\r
+ var instanceName = this.chartData[numberInstances].testInstanceName;\r
+ InstanceStrings[instanceName] = 0;\r
+ }\r
+\r
+\r
+ //Go through the data\r
+ for (let i = 0; i < rawData.length; i++) {\r
+\r
+ //for the first iteration, \r
+ if (i == 0) {\r
+ formattedData.push(rawData[0]);\r
+\r
+ // if the date is before the startDate specified by the filter or two months be default. \r
+ if (formattedData[0].date > this.stats.filters.startDate) {\r
+\r
+ // Go through the difference in days and push the date and count of 0. \r
+ let days = this.daysDuration(this.stats.filters.startDate, formattedData[0].date)\r
+ for (let k = days - 1; k >= 0; k--) {\r
+ let newDate = this.addDaysToDate(this.stats.filters.startDate, k);\r
+ var objectToAdd = {\r
+ date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+ };\r
+ //push the new date and all the strings for the test instances.\r
+ objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+\r
+ //add date to the beginning of the array. \r
+ formattedData.unshift(objectToAdd)\r
+\r
+ }\r
+ }\r
+\r
+ //for all other iterations\r
+ } else {\r
+\r
+ //get the difference in days.\r
+ let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);\r
+ if (days > 1) {\r
+ //push the new dates. \r
+ for (let j = 1; j < days; j++) {\r
+ let newDate = this.addDaysToDate(rawData[i - 1].date, j);\r
+ var objectToAdd = {\r
+ date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+ };\r
+ //push the new date and all the strings for the test instances.\r
+ objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+ formattedData.push(objectToAdd);\r
+ }\r
+ }\r
+ formattedData.push(rawData[i]);\r
+ }\r
+ }\r
+\r
+ if (rawData.length >= 1) {\r
+ var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);\r
+ if (days >= 1) {\r
+ for (let j = 1; j < days; j++) {\r
+ let newDate = this.addDaysToDate(rawData[rawData.length - 1].date, j);\r
+ var objectToAdd = {\r
+ date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),\r
+ };\r
+ objectToAdd = Object.assign(objectToAdd, InstanceStrings);\r
+ formattedData.push(objectToAdd);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ resolve(formattedData);\r
+ })\r
+ }\r
+\r
+ daysDuration(date1, date2) {\r
+ return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));\r
+ }\r
+\r
+ addDaysToDate(date, days) {\r
+ let newDate = new Date(date);\r
+ newDate.setDate(date.getDate() + days);\r
+ return newDate;\r
+ }\r
+\r
+ //initialize loading indicator\r
+ showLoadingIndicator() {\r
+\r
+ this.height = "380px";\r
+ this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);\r
+ this.loadingIndicator.background.fill = am4core.color("#fff");\r
+ this.loadingIndicator.background.fillOpacity = 0.8;\r
+ this.loadingIndicator.width = am4core.percent(100);\r
+ this.loadingIndicator.height = am4core.percent(100);\r
+\r
+ let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);\r
+ indicatorLabel.text = "Loading..";\r
+ indicatorLabel.align = "center";\r
+ indicatorLabel.valign = "middle";\r
+ indicatorLabel.fontSize = 18;\r
+ indicatorLabel.fontWeight= "bold";\r
+ indicatorLabel.dy = 50;\r
+\r
+ let loadingImage = this.loadingIndicator.createChild(am4core.Image);\r
+ //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";\r
+ loadingImage.href = "https://loading.io/spinners/equalizer/lg.equalizer-bars-loader.gif";\r
+ //loadingImage.dataSource = "/loading-pies.svg"\r
+ loadingImage.align = "center";\r
+ loadingImage.valign = "middle";\r
+ loadingImage.horizontalCenter = "middle";\r
+ loadingImage.verticalCenter = "middle";\r
+ loadingImage.scale = 3.0;\r
+\r
+ }\r
+\r
+ hideLoadingIndicator() {\r
+ this.loadingIndicator.hide();\r
+ }\r
+\r
+ async renderChart() {\r
+ //console.log("here")\r
+\r
+ am4core.options.minPolylineStep = 5;\r
+\r
+ this.chart = am4core.create(this.MultiLineChartDiv.nativeElement, am4charts.XYChart);\r
+ this.chart.hiddenState.properties.opacity = 0; // this creates initial fade-in\r
+\r
+ this.chart.paddingRight = 20;\r
+ this.chartData = this.stats.getData("multiLineData");\r
+\r
+ //reformat the data to match the format needed for amcharts. \r
+ var formattedData = this.reformatData();\r
+\r
+ //sort the data.\r
+ formattedData.sort((a, b) => a.date - b.date);\r
+\r
+ //fill in gaps in the data\r
+ await this.setupPoints(formattedData).then(res => {\r
+ formattedData = res;\r
+ }, err => console.log(err));\r
+\r
+ this.chart.data = formattedData;\r
+\r
+ let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());\r
+ dateAxis.title.text = "Date";\r
+\r
+ let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());\r
+ valueAxis.title.text = "Executions";\r
+\r
+ this.chart.cursor = new am4charts.XYCursor();\r
+ this.chart.cursor.xAxis = dateAxis;\r
+\r
+ //if the data is empty, push in a line and set the count to 0. \r
+ if (this.chartData.length == 0) {\r
+ this.chartData.push({ testInstanceName: "empty" })\r
+ let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+ newSeries.name = "empty";\r
+ newSeries.dataFields.dateX = "date";\r
+ newSeries.dataFields.valueY = "count";\r
+ newSeries.tooltipText = "{valueY.value}";\r
+ newSeries.sequencedInterpolation = true;\r
+ newSeries.defaultState.transitionDuration = 1000;\r
+ newSeries.strokeWidth = 3;\r
+ newSeries.tensionX = 0.8;\r
+ } else {\r
+\r
+ //initialize the lines for the series\r
+ for (let index = 0; index < this.chartData.length; index++) {\r
+ let newSeries = this.chart.series.push(new am4charts.LineSeries());\r
+ newSeries.name = this.chartData[index].testInstanceName;\r
+ newSeries.dataFields.dateX = "date";\r
+ newSeries.dataFields.valueY = (this.chartData[index].testInstanceName).toString();\r
+ newSeries.tooltipText = "{valueY.value}";\r
+ newSeries.sequencedInterpolation = true;\r
+ newSeries.defaultState.transitionDuration = 1000;\r
+ newSeries.strokeWidth = 3;\r
+ newSeries.tensionX = 0.8;\r
+ }\r
+ this.chart.legend = new am4charts.Legend();\r
+ this.chart.legend.labels.template.text = "[bold {color}]{name}";\r
+ }\r
+ }\r
+\r
+}
\ No newline at end of file