import { Component, OnInit, ViewChild } from '@angular/core'; import { PlotService, PlotType, PlotRequest, PlotResponse, TagField, FilterDefaults, DataType, YAxisDefinition, AxesTypes, PlotConfig } from '../plot.service'; import { UntypedFormControl, Validators } from '@angular/forms'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { LimitByComponent } from '../limit-by/limit-by.component'; import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component'; import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component'; import { PlotViewComponent, SelectionRange, DateAnchor } from '../plot-view/plot-view.component'; import { GalleryViewComponent } from '../gallery-view/gallery-view.component'; import * as moment from 'moment'; @Component({ selector: 'pdb-visualization-page', templateUrl: './visualization-page.component.html', styleUrls: ['./visualization-page.component.scss'] }) export class VisualizationPageComponent implements OnInit { readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS dateRange = new UntypedFormControl('2019-10-05 00:00:00 - 2019-10-11 23:59:59'); selectedPlotType = new Array(); plotTypes: Array = []; tagFields: Array = new Array(); groupBy = new Array(); @ViewChild('limitbycomponent') private limitbycomponent! : LimitByComponent; @ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent }) private y1AxisDefinitionComponent! : YAxisDefinitionComponent; @ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent }) private y2AxisDefinitionComponent! : YAxisDefinitionComponent; @ViewChild('query') query!: QueryAutocompleteComponent; @ViewChild('plotView') plotView!: PlotViewComponent; @ViewChild('galleryView') galleryView!: GalleryViewComponent; enableGallery = false; splitBy : TagField | undefined = undefined; y2AxisAvailable = false; intervalUnit = 'NO_INTERVAL'; intervalValue = 1; renderBarChartTickLabels = false; submitterId = crypto.randomUUID(); plotJobActive = false; constructor(private plotService: PlotService, private snackBar: MatSnackBar) { } showError(message:string) { this.snackBar.open(message, "", { duration: 5000, verticalPosition: 'top' }); } ngOnInit() { const that = this; (window).initDatePicker(); this.plotTypes = this.plotService.getPlotTypes(); this.selectedPlotType.push(this.plotTypes[0]); that.plotService.getFilterDefaults().subscribe(function(filterDefaults: FilterDefaults) { filterDefaults.fields.forEach(function(name:string) { that.tagFields.push(new TagField(name)); }, (error: any) => { that.showError(error.error.message); }); that.groupBy = that.tagFields.filter(val => filterDefaults.groupBy.includes(val.name)); that.splitBy = that.tagFields.find(val => filterDefaults.splitBy == val.name); }); } changePlotType(selectedPlotTypes: Array) { const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes)); this.plotTypes.forEach(pt => pt.active=false); compatiblePlotTypes.forEach(pt => pt.active=true); const axesTypes = this.getAxes(); this.y2AxisAvailable = axesTypes.y.length == 2; } selectedPlotTypesContains(plotTypeIds: Array){ return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0; } dateRangeAsString() : string { return (document.getElementById("search-date-range")).value; } gallery(){ if (this.splitBy != null){ this.plotView.imageUrl = ''; this.plotView.stats = null; this.galleryView.show=true; const request = this.createPlotRequest(); this.galleryView.renderGallery(request, this.splitBy.name); } else { console.error("variable splitBy was null when rendering gallery"); } } getAxes() : AxesTypes { const x = new Array(); const y = new Array(); for(var i = 0; i < this.selectedPlotType.length; i++){ var plotType = this.selectedPlotType[i]; if (!x.includes(plotType.xAxis)) { x.push(plotType.xAxis); } if (!y.includes(plotType.yAxis)) { y.push(plotType.yAxis); } } return new AxesTypes(x,y); } abort() { this.plotService.abort(this.submitterId).subscribe({ complete: () => { this.plotView.imageUrl = ''; this.plotView.stats = null; this.plotJobActive = false; this.showError("Job aborted"); document.dispatchEvent(new Event("invadersPause", {})); } }); } plot(){ const that = this; that.plotView.imageUrl = ''; that.plotView.stats = null; this.plotJobActive = true; this.plotView.axes = this.getAxes(); console.log(JSON.stringify(this.getAxes())); that.galleryView.show=false; document.dispatchEvent(new Event("invadersStart", {})); const request = this.createPlotRequest(); this.plotService.sendPlotRequest(request).subscribe({ next: (plotResponse: PlotResponse) => { this.plotView.imageUrl = "http://"+window.location.hostname+':'+window.location.port+'/'+plotResponse.imageUrl; this.plotView.stats = plotResponse.stats; this.plotJobActive = false; document.dispatchEvent(new Event("invadersPause", {})); }, error: (error:any) => { console.log(JSON.stringify(error)); this.plotView.imageUrl = ''; this.plotView.stats = null; this.plotJobActive = false; this.showError(error.error.message); document.dispatchEvent(new Event("invadersPause", {})); } }); } createPlotRequest(): PlotRequest { const aggregates = new Array(); this.selectedPlotType.forEach(a => aggregates.push(a.id)); const y1 = this.y1AxisDefinitionComponent.getAxisDefinition(); const y2 = this.y2AxisDefinitionComponent ? this.y2AxisDefinitionComponent.getAxisDefinition() : undefined; const results = document.getElementById("results"); const config = new PlotConfig( this.query.query, this.groupBy.map(o => o.name), this.limitbycomponent.limitBy, this.limitbycomponent.limit, y1, y2, this.dateRangeAsString(), // dateRange aggregates, // aggregates this.intervalUnit, this.intervalValue, this.renderBarChartTickLabels, ); const request = new PlotRequest( results != null ? results.offsetHeight-1: 1024, results != null ? results.offsetWidth-1 : 1024, 300, // thumbnailMaxWidth 200, // thumbnailMaxHeight false, // keyOutside this.enableGallery, // generateThumbnail this.submitterId, config); return request; } /** * Zoom in/out by zoomFaktor, so that the anchorInPercentOfDateRange keeps the same position. * * shiftDateByAnchor(dateRangeAsString, 0.20, 0.5) zooms in by 50%, so that the date that was at 20% before the zoom is still at 20% after the zoom * shiftDateByAnchor(dateRangeAsString, 0.33, 2) zooms out by 50%, so that the date that was at 33% before the zoom is still at 33% after the zoom */ shiftDateByAnchor(dateRange:string, anchorInPercentOfDateRange:number, zoomFactor:number) { const dateRangeParsed = this.parseDateRange(dateRange); const dateRangeInSeconds = dateRangeParsed.duration.asSeconds(); const anchorTimestampInSeconds = dateRangeParsed.startDate.clone().add(Math.floor(dateRangeInSeconds*anchorInPercentOfDateRange), "seconds"); const newDateRangeInSeconds = dateRangeInSeconds * zoomFactor; const newStartDate = anchorTimestampInSeconds.clone().subtract(newDateRangeInSeconds*anchorInPercentOfDateRange, "seconds"); const newEndDate = newStartDate.clone().add({seconds: newDateRangeInSeconds});; this.setDateRange(newStartDate, newEndDate); } /** * Zoom in/out or shift date by adding factorStartDate*dateRangeInSeconds seconds to the start date * and factorEndDate*dateRangeInSeconds seconds to the end date. * * shiftDate(dateRangeAsString, 0.25, -0.25) will zoom in, making the range half its size * shiftDate(dateRangeAsString, -0.5, 0.5) will zoom out, making the range double its size * shiftDate(dateRangeAsString, -0.5, -0.5) will move the range by half its size to older values * shiftDate(dateRangeAsString, 1, 1) will move the range by its size to newer values */ shiftDate(dateRange: string, factorStartDate: number, factorEndDate: number) { const dateRangeParsed = this.parseDateRange(dateRange); const dateRangeInSeconds = dateRangeParsed.duration.asSeconds(); const newStartDate = dateRangeParsed.startDate.add({seconds: dateRangeInSeconds*factorStartDate}); const newEndDate = dateRangeParsed.endDate.add({seconds: dateRangeInSeconds*factorEndDate}); this.setDateRange(newStartDate, newEndDate); } parseDateRange(dateRangeAsString : string) : DateRange { const startDate = moment(dateRangeAsString.slice(0, 19)); const endDate = moment(dateRangeAsString.slice(22, 41)); return { startDate: startDate, endDate: endDate, duration: moment.duration(endDate.diff(startDate)) }; } setDateRange(startDate: any, endDate: any) { const formattedStartDate = startDate.format(this.DATE_PATTERN); const formattedEndDate = endDate.format(this.DATE_PATTERN); const newDateRange = formattedStartDate+" - "+formattedEndDate; (document.getElementById("search-date-range")).value = newDateRange; this.plot(); } zoomRange(range: SelectionRange) { this.shiftDate(this.dateRangeAsString(), range.startPercentOfDateRange, range.endPercentOfDateRange-1); } zoomWithDateAnchor(dateAnchor: DateAnchor){ this.shiftDateByAnchor(this.dateRangeAsString(), dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor); } } export class DateRange { startDate: any; endDate: any; duration: any; } /* export class AxesUsed { x1: DataType; y1: DataType; x2: DataType; y2: DataType; } */