fix-statistics #89

Merged
sebastian merged 4 commits from fix-statistics into master 2023-12-20 18:29:52 +01:00
11 changed files with 403 additions and 139 deletions
Showing only changes of commit 08352add9d - Show all commits

View File

@ -83,6 +83,8 @@ import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/dra
import { TaskgroupActivityComponent } from './statistics/taskgroup-activity/taskgroup-activity.component'; import { TaskgroupActivityComponent } from './statistics/taskgroup-activity/taskgroup-activity.component';
import {NgxSliderModule} from "ngx-slider-v2"; import {NgxSliderModule} from "ngx-slider-v2";
import {NgApexchartsModule} from "ng-apexcharts"; 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({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -124,6 +126,8 @@ import {NgApexchartsModule} from "ng-apexcharts";
DateTimePickerComponent, DateTimePickerComponent,
DraggableSchedulerComponent, DraggableSchedulerComponent,
TaskgroupActivityComponent, TaskgroupActivityComponent,
SimpleActivityDiagramComponent,
HeatmapActivityComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -0,0 +1,8 @@
<div style="text-align:center">
<apx-chart
[series]="chartOptions.series!"
[chart]="chartOptions.chart!"
[xaxis]="chartOptions.xaxis!"
[title]="chartOptions.title!"
></apx-chart>
</div>

View File

@ -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<HeatmapActivityComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [HeatmapActivityComponent]
});
fixture = TestBed.createComponent(HeatmapActivityComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<ChartOptions> = this.generateChartOptions()
@Input() selectedTaskgroupPath?: TaskgroupPathInfo
constructor(private historyService: HistoryService) {
}
ngOnInit() {
this.chartOptions = this.generateChartOptions();
}
generateChartOptions(): Partial<ChartOptions> {
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();
}
}

View File

@ -0,0 +1,12 @@
<div style="text-align:center">
<apx-chart
[series]="chartOptions.series!"
[chart]="chartOptions.chart!"
[xaxis]="chartOptions.xaxis!"
[title]="chartOptions.title!"
></apx-chart>
</div>
<div class="custom-slider">
<ngx-slider class="ngx-slider" [options]="options" [formControl]="sliderControl"
(userChange)="onUserChange($event)"></ngx-slider>
</div>

View File

@ -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<SimpleActivityDiagramComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SimpleActivityDiagramComponent]
});
fixture = TestBed.createComponent(SimpleActivityDiagramComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<ChartOptions> = 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<ChartOptions> {
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();
}
}

View File

@ -1,28 +1,18 @@
<div class="container"> <div class="container">
<app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list> <app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
<mat-form-field style="width: 90%"> <mat-form-field style="width: 90%">
<mat-label>Toppings</mat-label> <mat-label>Taskgroup</mat-label>
<mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="updateSerieSelection()"> <mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="onSelectTaskgroupPath()">
<mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option> <mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field style="width: 10%;"> <mat-form-field style="width: 10%;">
<mat-label>Toppings</mat-label> <mat-label>ChartType</mat-label>
<mat-select [(ngModel)]="selectedChartype" (ngModelChange)="updateSerieSelection()"> <mat-select [(ngModel)]="selectedChartype" (ngModelChange)="onSelectChartType()">
<mat-option *ngFor="let topping of availableChartTypes" [value]="topping">{{topping}}</mat-option> <mat-option *ngFor="let topping of availableChartTypes" [value]="topping">{{topping}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div style="text-align:center"> <app-simple-activity-diagram *ngIf="selectedChartype !== 'heatmap'" #simpleActivityDiagram [selectedChartype]="selectedChartype" [selectedTaskgroupPath]="selectedTaskgroupPath"></app-simple-activity-diagram>
<apx-chart <app-heatmap-activity *ngIf="selectedChartype === 'heatmap'" #heatMap></app-heatmap-activity>
[series]="chartOptions.series!"
[chart]="chartOptions.chart!"
[xaxis]="chartOptions.xaxis!"
[title]="chartOptions.title!"
></apx-chart>
</div>
<div class="custom-slider">
<ngx-slider class="ngx-slider" [options]="options" [formControl]="sliderControl" (userChangeStart)="onUserChangeStart($event)"
(userChangeEnd)="onUserChangeStop($event)" (userChange)="onUserChange($event)"></ngx-slider>
</div>
</div> </div>

View File

@ -7,17 +7,22 @@ import {
ApexAxisChartSeries, ApexAxisChartSeries,
ApexChart, ApexChart,
ApexXAxis, ApexXAxis,
ApexTitleSubtitle, ChartType ApexTitleSubtitle, ChartType, ApexPlotOptions, ApexDataLabels
} from "ng-apexcharts"; } from "ng-apexcharts";
import {timeInterval} from "rxjs"; import {timeInterval} from "rxjs";
import {FormControl} from "@angular/forms"; import {FormControl} from "@angular/forms";
import {HistoryService, TaskgroupPathInfo, TaskgroupService} from "../../../api"; 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 = { export type ChartOptions = {
series: ApexAxisChartSeries; series: ApexAxisChartSeries;
chart: ApexChart; chart: ApexChart;
xaxis: ApexXAxis; xaxis: ApexXAxis;
title: ApexTitleSubtitle; title: ApexTitleSubtitle;
plotOptions: ApexPlotOptions,
colors: any,
dataLabels: ApexDataLabels;
}; };
@Component({ @Component({
selector: 'app-taskgroup-activity', selector: 'app-taskgroup-activity',
@ -40,32 +45,13 @@ export class TaskgroupActivityComponent implements OnInit{
} }
]; ];
@ViewChild('simpleActivityDiagram') simpleActivityDiagram ?: SimpleActivityDiagramComponent
@ViewChild('heatMap') heatMap ?: HeatmapActivityComponent
selectedChartype: string = "bar"; selectedChartype: string = "bar";
availableChartTypes: string[] = ["bar", "line", "area"] availableChartTypes: string[] = ["bar", "line", "area", "heatmap"]
selectedTaskgroupPath: TaskgroupPathInfo | undefined selectedTaskgroupPath: TaskgroupPathInfo | undefined
taskgroupPaths: TaskgroupPathInfo[] = [] 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<ChartOptions> = this.generateChartOptions()
constructor(private taskgroupService: TaskgroupService,
private historyService: HistoryService) {
}
ngOnInit() { ngOnInit() {
this.taskgroupService.taskgroupsPathGet().subscribe({ 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) { onSelectTaskgroupPath() {
const dates: Date[] = []; if(this.simpleActivityDiagram != undefined) {
let currentDate = moment(minValue); this.simpleActivityDiagram.setSelectedTaskgroupPath(this.selectedTaskgroupPath!);
while(currentDate <= maxValue) {
dates.push(currentDate.toDate());
currentDate = currentDate.add(1, 'days');
} }
return dates; this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!);
} }
onUserChangeStart(changeContext: ChangeContext) { onSelectChartType() {
if(this.simpleActivityDiagram != undefined) {
//console.log("onUserChangeStart" + new Date(changeContext.value)); this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype);
}
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<ChartOptions> {
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)
}
);
}
})
} }
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()
}
} }