fix-statistics #89
@ -23,6 +23,7 @@ import java.time.LocalDateTime;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@ -59,7 +60,14 @@ public class StatisticController {
 | 
			
		||||
            return ResponseEntity.status(404).body(new SimpleStatusResponse("failed"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ResponseEntity.ok(taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups,
 | 
			
		||||
                LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"))));
 | 
			
		||||
        List<TaskgroupActivityInfo> activityInfos = taskgroupPermissionResult.getResult().calcActivityInfo(includeSubTaskgroups,
 | 
			
		||||
                LocalDate.parse(startingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")), LocalDate.parse(endingDate, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
 | 
			
		||||
        activityInfos.sort(new Comparator<TaskgroupActivityInfo>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public int compare(TaskgroupActivityInfo o1, TaskgroupActivityInfo o2) {
 | 
			
		||||
                return o1.getDate().compareTo(o2.getDate());
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return ResponseEntity.ok(activityInfos);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,4 +17,12 @@ public class TaskgroupActivityInfo {
 | 
			
		||||
        this.date = localDate;
 | 
			
		||||
        this.activeMinutes = activeMinutes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public LocalDate getDate() {
 | 
			
		||||
        return date;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getActiveMinutes() {
 | 
			
		||||
        return activeMinutes;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,9 @@ public class TaskgroupPathInfo {
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private String taskgroupPath;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private TaskgroupEntityInfo rootTasktroup;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty
 | 
			
		||||
    private List<TaskgroupEntityInfo> directChildren;
 | 
			
		||||
    public TaskgroupPathInfo(Taskgroup taskgroup) {
 | 
			
		||||
@ -21,6 +24,7 @@ public class TaskgroupPathInfo {
 | 
			
		||||
            stringBuilder.append("/");
 | 
			
		||||
        }
 | 
			
		||||
        this.taskgroupPath = stringBuilder.substring(0, stringBuilder.length()-1);
 | 
			
		||||
        this.rootTasktroup = new TaskgroupEntityInfo(taskgroup);
 | 
			
		||||
        directChildren = taskgroup.getChildren().stream().map(TaskgroupEntityInfo::new).toList();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,5 +18,6 @@ export interface TaskgroupPathInfo {
 | 
			
		||||
     */
 | 
			
		||||
    taskgroupPath: string;
 | 
			
		||||
    directChildren: Array<TaskgroupEntityInfo>;
 | 
			
		||||
    rootTasktroup: TaskgroupEntityInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -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().add(2, 'days').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
 | 
			
		||||
            }
 | 
			
		||||
            console.log(currentDate)
 | 
			
		||||
            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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,28 +1,18 @@
 | 
			
		||||
<div class="container">
 | 
			
		||||
  <app-navigation-link-list #navLinkList [navigationLinks]="defaultNavigationLinkPath"></app-navigation-link-list>
 | 
			
		||||
  <mat-form-field style="width: 90%">
 | 
			
		||||
    <mat-label>Toppings</mat-label>
 | 
			
		||||
    <mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="updateSerieSelection()">
 | 
			
		||||
    <mat-label>Taskgroup</mat-label>
 | 
			
		||||
    <mat-select [(ngModel)]="selectedTaskgroupPath" (ngModelChange)="onSelectTaskgroupPath()">
 | 
			
		||||
      <mat-option *ngFor="let topping of taskgroupPaths" [value]="topping">{{topping.taskgroupPath}}</mat-option>
 | 
			
		||||
    </mat-select>
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
  <mat-form-field style="width: 10%;">
 | 
			
		||||
    <mat-label>Toppings</mat-label>
 | 
			
		||||
    <mat-select [(ngModel)]="selectedChartype" (ngModelChange)="updateSerieSelection()">
 | 
			
		||||
    <mat-label>ChartType</mat-label>
 | 
			
		||||
    <mat-select [(ngModel)]="selectedChartype" (ngModelChange)="onSelectChartType()">
 | 
			
		||||
      <mat-option *ngFor="let topping of availableChartTypes" [value]="topping">{{topping}}</mat-option>
 | 
			
		||||
    </mat-select>
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
 | 
			
		||||
  <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" (userChangeStart)="onUserChangeStart($event)"
 | 
			
		||||
               (userChangeEnd)="onUserChangeStop($event)" (userChange)="onUserChange($event)"></ngx-slider>
 | 
			
		||||
 </div>
 | 
			
		||||
  <app-simple-activity-diagram *ngIf="selectedChartype !== 'heatmap'" #simpleActivityDiagram [selectedChartype]="selectedChartype" [selectedTaskgroupPath]="selectedTaskgroupPath"></app-simple-activity-diagram>
 | 
			
		||||
  <app-heatmap-activity *ngIf="selectedChartype === 'heatmap'" #heatMap></app-heatmap-activity>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -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<ChartOptions> = this.generateChartOptions()
 | 
			
		||||
 | 
			
		||||
  constructor(private taskgroupService: TaskgroupService,
 | 
			
		||||
              private historyService: HistoryService) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.taskgroupService.taskgroupsPathGet().subscribe({
 | 
			
		||||
@ -75,82 +61,22 @@ export class TaskgroupActivityComponent implements OnInit{
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  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[] = [];
 | 
			
		||||
    const numberDays = maxValue.diff(minValue, 'days');
 | 
			
		||||
    for(let i=0; i<=numberDays; i++) {
 | 
			
		||||
      dates.push(minValue.add(i, 'd').toDate());
 | 
			
		||||
    }
 | 
			
		||||
    return dates;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUserChangeStart(changeContext: ChangeContext) {
 | 
			
		||||
 | 
			
		||||
      //console.log("onUserChangeStart" + new Date(changeContext.value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUserChangeStop(changeContext: ChangeContext) {
 | 
			
		||||
      //console.log("onUserChangeStop" + new Date(changeContext.highValue!))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onUserChange(changeContext: ChangeContext) {
 | 
			
		||||
    const dateRange = 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 = []
 | 
			
		||||
    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)
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    })
 | 
			
		||||
    return series;
 | 
			
		||||
  constructor(private taskgroupService: TaskgroupService) {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateSerieSelection() {
 | 
			
		||||
    this.chartOptions = this.generateChartOptions()
 | 
			
		||||
  onSelectTaskgroupPath() {
 | 
			
		||||
    if(this.simpleActivityDiagram != undefined) {
 | 
			
		||||
      this.simpleActivityDiagram.setSelectedTaskgroupPath(this.selectedTaskgroupPath!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.heatMap?.setSelectedTaskgroupPath(this.selectedTaskgroupPath!);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSelectChartType() {
 | 
			
		||||
    if(this.simpleActivityDiagram != undefined) {
 | 
			
		||||
      this.simpleActivityDiagram.setSelectedChartType(this.selectedChartype);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2612,6 +2612,7 @@ components:
 | 
			
		||||
      required:
 | 
			
		||||
        - taskgroupPath
 | 
			
		||||
        - directChildren
 | 
			
		||||
        - rootTasktroup
 | 
			
		||||
      additionalProperties: false
 | 
			
		||||
      properties:
 | 
			
		||||
        taskgroupPath:
 | 
			
		||||
@ -2621,6 +2622,9 @@ components:
 | 
			
		||||
          type: array
 | 
			
		||||
          items:
 | 
			
		||||
            $ref: '#/components/schemas/TaskgroupEntityInfo'
 | 
			
		||||
        rootTasktroup:
 | 
			
		||||
          type: object
 | 
			
		||||
          $ref: '#/components/schemas/TaskgroupEntityInfo'
 | 
			
		||||
    TaskgroupActivityInfo:
 | 
			
		||||
      required:
 | 
			
		||||
        - date
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user