import {Component, ElementRef, HostListener, OnDestroy, OnInit, QueryList, Renderer2, ViewChildren} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {map, tap} from 'rxjs/operators'; import * as moment from 'moment-timezone'; import {GraphLoader} from '../../shared/graph-loading/graphloader'; import {SensorsService} from '../../shared/api/endpoints/services/sensors.service'; import {HttpResponse} from '@angular/common/http'; import {ToastService} from '../../shared/services/toast.service'; import {Sensor} from '../../shared/api/endpoints/models/sensor'; import {ObservationService} from '../../shared/api/endpoints/services/observation.service'; import {SensorType} from '../../shared/api/endpoints/models/sensor-type'; import {Subscription} from 'rxjs'; import type {View} from 'vega'; @Component({ selector: 'app-unit', templateUrl: './unit.component.html', styleUrls: ['./unit.component.scss'] }) export class UnitComponent implements OnInit, OnDestroy { preselectedSensors: string; unitId: number; viewCount = 0; data = []; time = []; from: Date = moment().hour(0).minutes(0).subtract(7, 'days').toDate(); to: Date = moment().toDate(); today: Date = moment().toDate(); observationsData: any[] = []; sensorGroups = []; selectedSensors: string[] = []; sensors: Sensor[]; dateChanged = false; sensorTypes: SensorType[]; unitDescription: string; subscription: Subscription[] = []; showIntervalError = false; // One flag per group showVega: Record = {}; showVegaCounter: Record = {}; graphViews: Record = {}; // Grab *all* containers that match the template ref vegaContainer @ViewChildren('vegaContainer') vegaContainers!: QueryList; constructor( private activatedRoute: ActivatedRoute, private sensorService: SensorsService, private toastService: ToastService, private observationService: ObservationService, private route: ActivatedRoute, private renderer: Renderer2 ) { this.getInitData(); // get unit sensors and prepare them for view this.sensorService.getUnitSensors({ unit_id: this.unitId }).pipe( tap(sens => { this.sensors = sens; this.sensors.sort((a, b) => a.sensorId - b.sensorId); }), tap(() => { if (this.sensors && this.sensors.length > 0) { this.sensors.forEach(sensor => { const sensorType = sensor.sensorId.toString().slice(0, 5); if (!this.sensorGroups.some(group => group === sensorType)) { // create sensor groups only for unit sensors this.sensorGroups.push(sensorType); setTimeout(() => { GraphLoader.getGraph(null, null, null, '#vega_container_' + sensor.sensorId.toString().slice(0, 5), null); }, 0); } }); } }) ).toPromise().then(); } /** * Fires on every resize event */ @HostListener('window:resize', ['$event']) public onWindowResize(event: UIEvent): void { this.onResize(); } private onResize() { const itemCount = Object.keys(this.graphViews).length; console.log('Number of entries:', itemCount); Object.entries(this.graphViews).forEach(([key, view]) => { // Process each pair here const sensorGroupElement = '#vega_container_' + key; const box = document.getElementById('vega_container_' + key); const boxWidth = box.getBoundingClientRect().width; const boxHeight = box.getBoundingClientRect().height; if (view) { const newWidth = box.getBoundingClientRect().width - 50; console.log('Key:', key, 'NewWidth:', newWidth); view.width(newWidth).height(300).runAsync(); } }); } /** * Unsubscribe after leaving */ ngOnDestroy(): void { this.subscription.forEach(subs => subs.unsubscribe()); } /** * Sets up default data */ getInitData() { this.route.queryParams.subscribe(params => { if (params.unitDescription) { this.unitDescription = params.unitDescription; } }); this.sensorService.getSensorTypes().toPromise().then(types => this.sensorTypes = types); this.unitId = parseInt(this.activatedRoute.snapshot.paramMap.get('unitId'), 10); } ngOnInit(): void { } /** * Shows get data button */ onDateChanged() { if (moment(this.to).diff(moment(this.from), 'months') > 6){ this.dateChanged = false; this.showIntervalError = true; } else if (this.to < this.from) { this.dateChanged = false; this.showIntervalError = true; } else{ this.dateChanged = true; this.showIntervalError = false; } } /** * Gets data based on selected time range */ showGraph(changedDate: boolean = true, changedSensor: string = null) { const range: Date[] = [this.from, this.to]; this.getObservations(range, changedDate, changedSensor); } /** * Check button handler. * @param sensorId checked sensorId * @param event event for getting if checked or unchecked */ async addSensorToGraph(sensorId: string, event) { const groupId = sensorId.toString().slice(0, 5); const sensorGroupElement = '#vega_container_' + groupId; // if the checkmark is checked then the graph will be displayed this.graphViews[groupId] = null; this.showVega[groupId] = false; if (event.checked.includes(sensorId.toString())) this.showVega[groupId] = true; const box = document.getElementById('vega_container_' + groupId); const boxWidth = box.getBoundingClientRect().width; const boxHeight = box.getBoundingClientRect().height; GraphLoader.setSize(boxWidth - 50, 300); if (!this.selectedSensors.find(sensId => sensId.toString().slice(0, 5) === groupId)) { // if group of sensors is empty show empty graph // GraphLoader.getAnalyticsGraph(null, null, null, sensorGroupElement); this.graphViews[groupId] = await GraphLoader.getGraph(null, null, null, sensorGroupElement, null); } else { // use observations data if (event.checked) { // if checked > add to graph if (this.observationsData.some(sens => sens.sensorId.toString() === sensorId)) { // if already data for selected sensor in memory this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId), this.filteredObservationData(groupId), this.filteredSensorsInfos(groupId), sensorGroupElement, false); } else { // get data from server for added sensor and show graph for selected sensors this.showGraph(false, sensorId); } } else { // remove sensor from graph this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId), this.filteredObservationData(groupId), this.filteredSensorsInfos(groupId), sensorGroupElement, false); } } // no data in graph -> the graph element will be destroyed if (!this.showVega[groupId]) { const container = document.getElementById(`vega_container_${groupId}`); if (container) { container.innerHTML = ''; this.graphViews[groupId] = null; } console.log(`Cleared children of #vega_container_${groupId}`); } } /** * Filter observations data only fro selected sensors. * @param sensorGroupId id of changed sensor group */ filteredObservationData(sensorGroupId: string): any { return this.observationsData.filter(sen => this.selectedSensors.includes(sen.sensorId.toString()) && sen.sensorId.toString().slice(0, 5) === sensorGroupId); } /** * Filter only selected sensors for group of sensors * @param sensorGroupId group of sensors */ filteredSelectedSensors(sensorGroupId: string): any { return this.selectedSensors.filter(sen => sen.toString().slice(0, 5) === sensorGroupId); } /** * Get sensors only for group * @param sensorGroupId group id */ filteredSensorsInfos(sensorGroupId: string): any { return this.sensors.filter(sen => this.selectedSensors.includes(sen.sensorId.toString()) && sen.sensorId.toString().slice(0, 5) === sensorGroupId); } /** * Gets data from observation endpoint * @param range from and to interval * @param changedDate determines if dates changed so we need refresh all data * @param changedSensorId if selecting sensor only fetch data for this server */ getObservations(range: Date[], changedDate: boolean, changedSensorId: string) { if (changedDate) { // if changed date we need new data for all sensors this.observationsData = []; // empty observation data this.selectedSensors.forEach(selectSens => { this.observationEndpointRequest(selectSens, range); }); } else { // add data for added sensor this.observationEndpointRequest(changedSensorId, range); } } /** * Endpoint request to get observation data for sensor * @param sensorId sensor id to get data * @param range from and to interval */ observationEndpointRequest(sensorId: string, range: Date[]) { this.observationService.getObservation$Response({ unit_id: this.unitId, sensor_id: parseInt(sensorId, 10), from: moment(range[0]).format('yyyy-MM-DD HH:mm:ssZ').slice(0, -3), to: moment(range[1]).format('yyyy-MM-DD HH:mm:ssZ').slice(0, -3) }).pipe( map((response: HttpResponse) => { if (response.status === 200) { return response.body; } else if (response.status === 204) { this.toastService.showWarningNoData(); return response.body; } else { return false; } }) ).subscribe( async observations => { if (observations) { const groupId = sensorId.toString().slice(0, 5); this.observationsData.push({ sensorId, sensor: this.sensors.find(sens => sens.sensorId.toString() === sensorId.toString()), data: observations }); const view = '#vega_container_' + sensorId.toString().slice(0, 5); this.graphViews[groupId] = await GraphLoader.getGraph(this.filteredSelectedSensors(groupId), this.filteredObservationData(groupId), this.filteredSensorsInfos(groupId), view, false); } }, err => this.toastService.showError(err.error.message)); } }