From 08352add9d72c079839c5bc995c146a60d495464 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 20 Dec 2023 18:25:34 +0100 Subject: [PATCH] Include headmap for statistics --- frontend/src/app/app.module.ts | 4 + .../heatmap-activity.component.css | 0 .../heatmap-activity.component.html | 8 + .../heatmap-activity.component.spec.ts | 21 +++ .../heatmap-activity.component.ts | 168 ++++++++++++++++++ .../simple-activity-diagram.component.css | 0 .../simple-activity-diagram.component.html | 12 ++ .../simple-activity-diagram.component.spec.ts | 21 +++ .../simple-activity-diagram.component.ts | 146 +++++++++++++++ .../taskgroup-activity.component.html | 22 +-- .../taskgroup-activity.component.ts | 140 ++------------- 11 files changed, 403 insertions(+), 139 deletions(-) create mode 100644 frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.css create mode 100644 frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html create mode 100644 frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.spec.ts create mode 100644 frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts create mode 100644 frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.css create mode 100644 frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html create mode 100644 frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.spec.ts create mode 100644 frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index ed4e568..d04b089 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -83,6 +83,8 @@ import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/dra import { TaskgroupActivityComponent } from './statistics/taskgroup-activity/taskgroup-activity.component'; import {NgxSliderModule} from "ngx-slider-v2"; import {NgApexchartsModule} from "ng-apexcharts"; +import { SimpleActivityDiagramComponent } from './statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component'; +import { HeatmapActivityComponent } from './statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component'; @NgModule({ declarations: [ AppComponent, @@ -124,6 +126,8 @@ import {NgApexchartsModule} from "ng-apexcharts"; DateTimePickerComponent, DraggableSchedulerComponent, TaskgroupActivityComponent, + SimpleActivityDiagramComponent, + HeatmapActivityComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.css b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html new file mode 100644 index 0000000..9c020bf --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.spec.ts b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.spec.ts new file mode 100644 index 0000000..2c3565f --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HeatmapActivityComponent } from './heatmap-activity.component'; + +describe('HeatmapActivityComponent', () => { + let component: HeatmapActivityComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [HeatmapActivityComponent] + }); + fixture = TestBed.createComponent(HeatmapActivityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts new file mode 100644 index 0000000..9933dff --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/heatmap-activity/heatmap-activity.component.ts @@ -0,0 +1,168 @@ +import {Component, Input, OnInit, ViewChild} from '@angular/core'; +import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts"; +import {ChartOptions} from "../taskgroup-activity.component"; +import * as moment from "moment"; +import {HistoryService, TaskgroupActivityInfo, TaskgroupPathInfo} from "../../../../api"; + + +interface XYData { + x: any, + y: any +} +@Component({ + selector: 'app-heatmap-activity', + templateUrl: './heatmap-activity.component.html', + styleUrls: ['./heatmap-activity.component.css'] +}) +export class HeatmapActivityComponent implements OnInit{ + + @ViewChild("chart") chart?: ChartComponent; + public chartOptions: Partial = this.generateChartOptions() + + @Input() selectedTaskgroupPath?: TaskgroupPathInfo + + constructor(private historyService: HistoryService) { + } + + ngOnInit() { + this.chartOptions = this.generateChartOptions(); + } + + generateChartOptions(): Partial { + return { + series: this.generateSeries(), + chart: { + height: 350, + type: 'heatmap', + }, + title: { + text: "" + }, + dataLabels: { + enabled: false + }, + colors: ["#008FFB"], + }; + } + + generateSeries() : ApexAxisChartSeries { + const series: ApexAxisChartSeries = [] + + if(this.selectedTaskgroupPath != undefined) { + const startingDate = new Date(2023, 4, 9); + const endDate = new Date(2023, 11, 20); + let activityInfos: TaskgroupActivityInfo[]; + this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( + this.selectedTaskgroupPath.rootTasktroup.taskgroupID, + moment().subtract(6, "months").format("YYYY-MM-DD"), + moment().format("YYYY-MM-DD"), + true + ).subscribe({ + next: resp => { + activityInfos = resp; + const data: XYData[][] = []; + let currentDate: moment.Moment = moment(activityInfos[0].date).subtract(moment(activityInfos[0].date).day(), 'days'); + + //Offset until data starts + let index = currentDate.day(); + while(currentDate < moment(activityInfos[0].date)) { + if(data[currentDate.day()] == undefined) { + data[currentDate.day()] = []; + } + + + data[currentDate.day()][index] = { + x: moment(currentDate).toDate(), + y: 0 + } + currentDate.add(1, 'days'); + } + + //inside data + activityInfos.forEach(activityInfo => { + const momentDate: moment.Moment = moment(activityInfo.date); + const seriesIndex = momentDate.day(); + + if(data[seriesIndex] == undefined) { + data[seriesIndex] = []; + } + + data[seriesIndex][index] = { + x: activityInfo.date, + y: activityInfo.activeMinutes + } + + + if(seriesIndex == 6) { + index++; + } + }) + + currentDate = moment(activityInfos[activityInfos.length-1].date); + currentDate = currentDate.add(1, "days"); + //offset outside data + for(let i=moment(activityInfos[activityInfos.length-1].date).day(); i<7; i++) { + data[i][index] = { + x: moment(currentDate).toDate(), + y: 0 + } + + currentDate = currentDate.add(1, "days"); + } + + + series.push({ + name: "Saturday", + data: data[6] + }); + series.push({ + name: "Friday", + data: data[5] + }); + series.push({ + name: "Thursday", + data: data[4] + }); + series.push({ + name: "Wednesday", + data: data[3] + }); + series.push({ + name: "Tuesday", + data: data[2] + }); + series.push({ + name: "Monday", + data: data[1] + }); + series.push({ + name: "Sunday", + data: data[0] + }); + } + }) + return series; + } else { + return series; + } + + } + + generateData(start: moment.Moment, end: moment.Moment) { + const data: TaskgroupActivityInfo[] = []; + let currentDate: moment.Moment = moment(start); + while(currentDate <= end) { + data.push({ + date: currentDate.format("YYYY-MM-DD"), + activeMinutes: Math.floor(Math.random() * (100 + 1)) + }) + currentDate = currentDate.add(1, 'days'); + } + return data; + } + + setSelectedTaskgroupPath(taskgroupPath: TaskgroupPathInfo) { + this.selectedTaskgroupPath = taskgroupPath; + this.chartOptions = this.generateChartOptions(); + } +} diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.css b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html new file mode 100644 index 0000000..bda266d --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.html @@ -0,0 +1,12 @@ +
+ +
+
+ +
diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.spec.ts b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.spec.ts new file mode 100644 index 0000000..393e5d2 --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SimpleActivityDiagramComponent } from './simple-activity-diagram.component'; + +describe('SimpleActivityDiagramComponent', () => { + let component: SimpleActivityDiagramComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SimpleActivityDiagramComponent] + }); + fixture = TestBed.createComponent(SimpleActivityDiagramComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts new file mode 100644 index 0000000..dc20fb4 --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/simple-activity-diagram/simple-activity-diagram.component.ts @@ -0,0 +1,146 @@ +import {Component, Input, ViewChild} from '@angular/core'; +import {FormControl} from "@angular/forms"; +import {ChangeContext, LabelType, Options} from "ngx-slider-v2"; +import {ApexAxisChartSeries, ChartComponent, ChartType} from "ng-apexcharts"; +import {HistoryService, TaskgroupPathInfo, TaskgroupService} from "../../../../api"; +import * as moment from "moment/moment"; +import {ChartOptions} from "../taskgroup-activity.component"; + +@Component({ + selector: 'app-simple-activity-diagram', + templateUrl: './simple-activity-diagram.component.html', + styleUrls: ['./simple-activity-diagram.component.css'] +}) +export class SimpleActivityDiagramComponent { + @Input('selectedChartype') selectedChartype: string = "bar"; + @Input() selectedTaskgroupPath: TaskgroupPathInfo | undefined; + + sliderControl: FormControl = new FormControl() + dateRange: Date[] = this.createDateRange(); + selectedDateRange: Date[] = this.dateRange; + value: number = this.dateRange[0].getTime(); + options: Options = { + stepsArray: this.dateRange.map((date: Date) => { + return { value: date.getTime() }; + }), + translate: (value: number, label: LabelType): string => { + return new Date(value).toDateString(); + }, + floor: 0, + ceil: this.dateRange.length, + }; + + @ViewChild("chart") chart?: ChartComponent; + public chartOptions: Partial = this.generateChartOptions() + + constructor(private taskgroupService: TaskgroupService, + private historyService: HistoryService) { + } + + createDateRange(): Date[] { + const dates: Date[] = []; + for (let i: number = 1; i <= 31; i++) { + + dates.push(moment().subtract(30-i, 'd').toDate()); + } + this.sliderControl.setValue([dates[0], dates[dates.length-1]]) + return dates; + } + + createDateRangeBetween(minValue: moment.Moment, maxValue: moment.Moment) { + const dates: Date[] = []; + let currentDate = moment(minValue); + + while(currentDate <= maxValue) { + dates.push(currentDate.toDate()); + currentDate = currentDate.add(1, 'days'); + } + + return dates; + } + + onUserChange(changeContext: ChangeContext) { + console.log("min " + moment(changeContext.value).format("YYYY-MM-DD")); + console.log("max " + moment(changeContext.highValue!).format("YYYY-MM-DD")) + this.selectedDateRange = this.createDateRangeBetween(moment(changeContext.value), moment(changeContext.highValue!)) + this.chartOptions = this.generateChartOptions() + } + + generateChartOptions(): Partial { + return { + series: this.generateSeries(), + chart: { + height: 350, + type: this.selectedChartype as ChartType, + stacked: true + }, + title: { + text: "" + }, + xaxis: { + categories: this.selectedDateRange.map(date => date.toDateString()) + } + }; + } + + generateSeries() : ApexAxisChartSeries { + const series: ApexAxisChartSeries = [] + + if(this.selectedTaskgroupPath != undefined) { + this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( + this.selectedTaskgroupPath!.rootTasktroup.taskgroupID, + moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), + moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), + false + ).subscribe({ + next: resp => { + series.push( + { + name: this.selectedTaskgroupPath!.rootTasktroup.taskgroupName, + data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) + } + ); + } + }) + } + + + + + this.selectedTaskgroupPath?.directChildren.forEach(taskgroup => { + + this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( + taskgroup.taskgroupID, + moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), + moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), true + ).subscribe({ + next: resp => { + series.push( + { + name: taskgroup.taskgroupName, + data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) + } + ) + } + }) + + }) + console.log(series); + return series; + + } + + updateSerieSelection() { + this.chartOptions = this.generateChartOptions() + } + + setSelectedChartType(selectedChartype: string) { + this.selectedChartype = selectedChartype; + this.updateSerieSelection(); + } + + setSelectedTaskgroupPath(selectedTaskgroupPath: TaskgroupPathInfo) { + this.selectedTaskgroupPath = selectedTaskgroupPath; + this.updateSerieSelection(); + } +} diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html index 39ee84d..59d25d6 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html @@ -1,28 +1,18 @@
- Toppings - + Taskgroup + {{topping.taskgroupPath}} - Toppings - + ChartType + {{topping}} -
- -
-
- -
+ +
diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts index 0631364..526fdf5 100644 --- a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts @@ -7,17 +7,22 @@ import { ApexAxisChartSeries, ApexChart, ApexXAxis, - ApexTitleSubtitle, ChartType + ApexTitleSubtitle, ChartType, ApexPlotOptions, ApexDataLabels } from "ng-apexcharts"; import {timeInterval} from "rxjs"; import {FormControl} from "@angular/forms"; import {HistoryService, TaskgroupPathInfo, TaskgroupService} from "../../../api"; +import {SimpleActivityDiagramComponent} from "./simple-activity-diagram/simple-activity-diagram.component"; +import {HeatmapActivityComponent} from "./heatmap-activity/heatmap-activity.component"; export type ChartOptions = { series: ApexAxisChartSeries; chart: ApexChart; xaxis: ApexXAxis; title: ApexTitleSubtitle; + plotOptions: ApexPlotOptions, + colors: any, + dataLabels: ApexDataLabels; }; @Component({ selector: 'app-taskgroup-activity', @@ -40,32 +45,13 @@ export class TaskgroupActivityComponent implements OnInit{ } ]; + @ViewChild('simpleActivityDiagram') simpleActivityDiagram ?: SimpleActivityDiagramComponent + @ViewChild('heatMap') heatMap ?: HeatmapActivityComponent selectedChartype: string = "bar"; - availableChartTypes: string[] = ["bar", "line", "area"] + availableChartTypes: string[] = ["bar", "line", "area", "heatmap"] selectedTaskgroupPath: TaskgroupPathInfo | undefined taskgroupPaths: TaskgroupPathInfo[] = [] - sliderControl: FormControl = new FormControl() - dateRange: Date[] = this.createDateRange(); - selectedDateRange: Date[] = this.dateRange; - value: number = this.dateRange[0].getTime(); - options: Options = { - stepsArray: this.dateRange.map((date: Date) => { - return { value: date.getTime() }; - }), - translate: (value: number, label: LabelType): string => { - return new Date(value).toDateString(); - }, - floor: 0, - ceil: this.dateRange.length, - }; - - @ViewChild("chart") chart?: ChartComponent; - public chartOptions: Partial = this.generateChartOptions() - - constructor(private taskgroupService: TaskgroupService, - private historyService: HistoryService) { - } ngOnInit() { this.taskgroupService.taskgroupsPathGet().subscribe({ @@ -75,114 +61,22 @@ export class TaskgroupActivityComponent implements OnInit{ }) } + constructor(private taskgroupService: TaskgroupService) { - createDateRange(): Date[] { - const dates: Date[] = []; - for (let i: number = 1; i <= 31; i++) { - - dates.push(moment().subtract(30-i, 'd').toDate()); - } - this.sliderControl.setValue([dates[0], dates[dates.length-1]]) - return dates; } - createDateRangeBetween(minValue: moment.Moment, maxValue: moment.Moment) { - const dates: Date[] = []; - let currentDate = moment(minValue); - - while(currentDate <= maxValue) { - dates.push(currentDate.toDate()); - currentDate = currentDate.add(1, 'days'); + onSelectTaskgroupPath() { + if(this.simpleActivityDiagram != undefined) { + this.simpleActivityDiagram.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); } - return dates; + this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!); } - onUserChangeStart(changeContext: ChangeContext) { - - //console.log("onUserChangeStart" + new Date(changeContext.value)); - } - - - onUserChangeStop(changeContext: ChangeContext) { - //console.log("onUserChangeStop" + new Date(changeContext.highValue!)) - } - - onUserChange(changeContext: ChangeContext) { - console.log("min " + moment(changeContext.value).format("YYYY-MM-DD")); - console.log("max " + moment(changeContext.highValue!).format("YYYY-MM-DD")) - this.selectedDateRange = this.createDateRangeBetween(moment(changeContext.value), moment(changeContext.highValue!)) - this.chartOptions = this.generateChartOptions() - } - - generateChartOptions(): Partial { - const series = this.generateSeries(); - - - return { - series: series, - chart: { - height: 350, - type: this.selectedChartype as ChartType, - stacked: true - }, - title: { - text: "" - }, - xaxis: { - categories: this.selectedDateRange.map(date => date.toDateString()) - } - }; - } - - generateSeries() : ApexAxisChartSeries { - const series: ApexAxisChartSeries = [] - - if(this.selectedTaskgroupPath != undefined) { - this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( - this.selectedTaskgroupPath!.rootTasktroup.taskgroupID, - moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), - moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), - false - ).subscribe({ - next: resp => { - series.push( - { - name: this.selectedTaskgroupPath!.rootTasktroup.taskgroupName, - data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) - } - ); - } - }) + onSelectChartType() { + if(this.simpleActivityDiagram != undefined) { + this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype); } - - - - - this.selectedTaskgroupPath?.directChildren.forEach(taskgroup => { - - this.historyService.statisticsTaskgroupActivityTaskgroupIDStartingDateEndingDateIncludeSubTaskgroupsGet( - taskgroup.taskgroupID, - moment(this.selectedDateRange[0]).format("YYYY-MM-DD"), - moment(this.selectedDateRange[this.selectedDateRange.length-1]).format("YYYY-MM-DD"), true - ).subscribe({ - next: resp => { - series.push( - { - name: taskgroup.taskgroupName, - data: resp.map(dailyActivityInfo => dailyActivityInfo.activeMinutes) - } - ) - } - }) - - }) - console.log(series); - return series; - } - updateSerieSelection() { - this.chartOptions = this.generateChartOptions() - } }