dashboard #1
@@ -1,5 +1,7 @@
|
|||||||
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
|
||||||
import { DataType, AxesTypes, PlotResponseStats } from '../plot.service';
|
import { DataType, AxesTypes, PlotResponseStats, PlotConfig, PlotService, PlotResponse, PlotRequest } from '../plot.service';
|
||||||
|
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-plot-view',
|
selector: 'pdb-plot-view',
|
||||||
@@ -8,6 +10,7 @@ import { DataType, AxesTypes, PlotResponseStats } from '../plot.service';
|
|||||||
})
|
})
|
||||||
export class PlotViewComponent implements OnInit {
|
export class PlotViewComponent implements OnInit {
|
||||||
|
|
||||||
|
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
||||||
|
|
||||||
readonly gnuplotLMargin = 110; // The left margin configured for gnuplot
|
readonly gnuplotLMargin = 110; // The left margin configured for gnuplot
|
||||||
readonly gnuplotRMargin = 110; // The right margin configured for gnuplot
|
readonly gnuplotRMargin = 110; // The right margin configured for gnuplot
|
||||||
@@ -21,10 +24,10 @@ export class PlotViewComponent implements OnInit {
|
|||||||
axes!: AxesTypes;
|
axes!: AxesTypes;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
zoomRange : EventEmitter<SelectionRange> = new EventEmitter<SelectionRange>();
|
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
zoomWithDateAnchor : EventEmitter<DateAnchor> = new EventEmitter<DateAnchor>();
|
dateRangeUpdateEvent : EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
in_drag_mode = false;
|
in_drag_mode = false;
|
||||||
drag_start_x = 0;
|
drag_start_x = 0;
|
||||||
@@ -41,11 +44,23 @@ export class PlotViewComponent implements OnInit {
|
|||||||
|
|
||||||
showStats = false;
|
showStats = false;
|
||||||
|
|
||||||
constructor() { }
|
config? : PlotConfig;
|
||||||
|
|
||||||
|
submitterId = crypto.randomUUID();
|
||||||
|
|
||||||
|
constructor(private service : PlotService, private snackBar: MatSnackBar) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
showError(message:string) {
|
||||||
|
this.snackBar.open(message, "", {
|
||||||
|
duration: 5000,
|
||||||
|
verticalPosition: 'top'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
hideZoomInSlider() {
|
hideZoomInSlider() {
|
||||||
this.zoomInSliderStyleDisplay = "none";
|
this.zoomInSliderStyleDisplay = "none";
|
||||||
}
|
}
|
||||||
@@ -147,7 +162,7 @@ export class PlotViewComponent implements OnInit {
|
|||||||
const startPercentOfDateRange = startPxWithinPlotArea / widthPlotArea;
|
const startPercentOfDateRange = startPxWithinPlotArea / widthPlotArea;
|
||||||
const endPercentOfDateRange = endPxWithinPlotArea / widthPlotArea;
|
const endPercentOfDateRange = endPxWithinPlotArea / widthPlotArea;
|
||||||
|
|
||||||
this.zoomRange.emit(new SelectionRange(startPercentOfDateRange, endPercentOfDateRange));
|
this.zoomRange(new SelectionRange(startPercentOfDateRange, endPercentOfDateRange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +177,25 @@ export class PlotViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDateRange(startDate: any, endDate: any) {
|
||||||
|
const formattedStartDate = startDate.format(this.DATE_PATTERN);
|
||||||
|
const formattedEndDate = endDate.format(this.DATE_PATTERN);
|
||||||
|
|
||||||
|
const newDateRange = formattedStartDate+" - "+formattedEndDate;
|
||||||
|
|
||||||
|
//(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
|
||||||
|
this.dateRangeUpdateEvent.emit(newDateRange);
|
||||||
|
//this.plot();
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomRange(range: SelectionRange) {
|
||||||
|
this.shiftDate(this.config?.dateRange!, range.startPercentOfDateRange, range.endPercentOfDateRange-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomWithDateAnchor(dateAnchor: DateAnchor){
|
||||||
|
this.shiftDateByAnchor(this.config?.dateRange!, dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
zoomByScroll(event: WheelEvent) {
|
zoomByScroll(event: WheelEvent) {
|
||||||
if (this.isInImage(event) && event.deltaY != 0 && this.axes.hasXAxis(DataType.Time)) {
|
if (this.isInImage(event) && event.deltaY != 0 && this.axes.hasXAxis(DataType.Time)) {
|
||||||
this.in_drag_mode = false;
|
this.in_drag_mode = false;
|
||||||
@@ -175,7 +209,7 @@ export class PlotViewComponent implements OnInit {
|
|||||||
|
|
||||||
const zoomFactor = event.deltaY < 0 ? 0.5 : 2;
|
const zoomFactor = event.deltaY < 0 ? 0.5 : 2;
|
||||||
|
|
||||||
this.zoomWithDateAnchor.emit(new DateAnchor(cursorPercentOfDateRange, zoomFactor));
|
this.zoomWithDateAnchor(new DateAnchor(cursorPercentOfDateRange, zoomFactor));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +220,100 @@ export class PlotViewComponent implements OnInit {
|
|||||||
hideDetails() {
|
hideDetails() {
|
||||||
this.showStats = false;
|
this.showStats = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plot(config : PlotConfig, axes: AxesTypes){
|
||||||
|
this.config = config;
|
||||||
|
this.axes = axes;
|
||||||
|
|
||||||
|
const request = this.createPlotRequest();
|
||||||
|
|
||||||
|
this.loadingEvent.emit(new LoadingEvent(true));
|
||||||
|
const x = this.service.sendPlotRequest(request).subscribe({
|
||||||
|
next: (plotResponse: PlotResponse) => {
|
||||||
|
this.imageUrl = "http://"+window.location.hostname+':'+window.location.port+'/'+plotResponse.imageUrl;
|
||||||
|
this.stats = plotResponse.stats;
|
||||||
|
document.dispatchEvent(new Event("invadersPause", {}));
|
||||||
|
this.loadingEvent.emit(new LoadingEvent(false));
|
||||||
|
},
|
||||||
|
error: (error:any) => {
|
||||||
|
this.imageUrl = '';
|
||||||
|
this.stats = null;
|
||||||
|
this.showError(error.error.message);
|
||||||
|
document.dispatchEvent(new Event("invadersPause", {}));
|
||||||
|
this.loadingEvent.emit(new LoadingEvent(false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlotRequest(): PlotRequest {
|
||||||
|
const results = document.getElementById("results");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const request = new PlotRequest(
|
||||||
|
results != null ? results.offsetHeight-1: 1024,
|
||||||
|
results != null ? results.offsetWidth-1 : 1024,
|
||||||
|
300, // thumbnailMaxWidth
|
||||||
|
200, // thumbnailMaxHeight
|
||||||
|
false, // keyOutside
|
||||||
|
false, // generateThumbnail
|
||||||
|
this.submitterId,
|
||||||
|
this.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))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SelectionRange {
|
export class SelectionRange {
|
||||||
@@ -207,3 +335,13 @@ export class DateAnchor {
|
|||||||
this.zoomFactor = zoomFactor;
|
this.zoomFactor = zoomFactor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class LoadingEvent {
|
||||||
|
constructor(public loading: boolean){}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DateRange {
|
||||||
|
startDate: any;
|
||||||
|
endDate: any;
|
||||||
|
duration: any;
|
||||||
|
}
|
||||||
@@ -96,8 +96,8 @@
|
|||||||
<div id="results">
|
<div id="results">
|
||||||
<pdb-plot-view
|
<pdb-plot-view
|
||||||
#plotView
|
#plotView
|
||||||
(zoomRange)="zoomRange($event)"
|
(loadingEvent)="loading($event)"
|
||||||
(zoomWithDateAnchor)="zoomWithDateAnchor($event)"></pdb-plot-view>
|
(dateRangeUpdateEvent)="updateDateRange($event)"></pdb-plot-view>
|
||||||
<pdb-gallery-view
|
<pdb-gallery-view
|
||||||
#galleryView>
|
#galleryView>
|
||||||
</pdb-gallery-view>
|
</pdb-gallery-view>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack
|
|||||||
import { LimitByComponent } from '../limit-by/limit-by.component';
|
import { LimitByComponent } from '../limit-by/limit-by.component';
|
||||||
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
|
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
|
||||||
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
|
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
|
||||||
import { PlotViewComponent, SelectionRange, DateAnchor } from '../plot-view/plot-view.component';
|
import { PlotViewComponent, SelectionRange, DateAnchor, LoadingEvent } from '../plot-view/plot-view.component';
|
||||||
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
|
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
@@ -89,6 +89,14 @@ export class VisualizationPageComponent implements OnInit {
|
|||||||
that.splitBy = that.tagFields.find(val => filterDefaults.splitBy == val.name);
|
that.splitBy = that.tagFields.find(val => filterDefaults.splitBy == val.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
loading(event: LoadingEvent) {
|
||||||
|
this.plotJobActive = event.loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDateRange(newDateRange: string) {
|
||||||
|
(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
|
||||||
changePlotType(selectedPlotTypes: Array<PlotType>) {
|
changePlotType(selectedPlotTypes: Array<PlotType>) {
|
||||||
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes));
|
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes));
|
||||||
@@ -151,34 +159,9 @@ export class VisualizationPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plot(){
|
plot(){
|
||||||
const that = this;
|
const config = this.createPlotConfig();
|
||||||
|
const axes = this.getAxes();
|
||||||
that.plotView.imageUrl = '';
|
this.plotView.plot(config, axes);
|
||||||
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", {}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlotConfig(): PlotConfig {
|
createPlotConfig(): PlotConfig {
|
||||||
@@ -220,89 +203,4 @@ export class VisualizationPageComponent implements OnInit {
|
|||||||
config);
|
config);
|
||||||
return request;
|
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;
|
|
||||||
|
|
||||||
(<HTMLInputElement>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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user