use date picker in visualization page
This commit is contained in:
@@ -1,5 +1,15 @@
|
|||||||
<style>
|
<style></style>
|
||||||
|
|
||||||
</style>
|
<form>
|
||||||
|
<app-date-picker
|
||||||
|
[formControl]="datePicker"
|
||||||
|
(dateValueSelected)="(dateChanged)"
|
||||||
|
></app-date-picker>
|
||||||
|
|
||||||
<app-date-picker></app-date-picker>
|
Type: {{ datePicker.value?.type }}<br />
|
||||||
|
value: {{ datePicker.value?.value }}<br />
|
||||||
|
display: {{ datePicker.value?.display }}<br />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button (click)="readValue()">read value</button>
|
||||||
|
<div>{{ output }}</div>
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
import { Component} from '@angular/core';
|
import { Component } from "@angular/core";
|
||||||
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
|
import { DateValue } from "./date-picker.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-date-picker-test',
|
selector: "app-date-picker-test",
|
||||||
templateUrl: './date-picker-test.component.html'
|
templateUrl: "./date-picker-test.component.html",
|
||||||
})
|
})
|
||||||
export class DatePickerTestComponent {
|
export class DatePickerTestComponent {
|
||||||
|
datePicker = new FormControl<DateValue>(
|
||||||
|
new DateValue(
|
||||||
|
"ABSOLUTE",
|
||||||
|
"2019-10-05 12:34:56 - 2019-10-11 23:59:59",
|
||||||
|
"2019-10-05 12:34:56 - 2019-10-11 23:59:59",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(){
|
output = "";
|
||||||
|
|
||||||
|
readValue() {
|
||||||
|
this.output = this.datePicker.value?.type + " " +
|
||||||
|
this.datePicker.value?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dateChanged(e: any) {
|
||||||
|
console.log("dateChanged", e);
|
||||||
|
this.output += "dateChanged: " + e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@
|
|||||||
<mat-tab label="Quick">
|
<mat-tab label="Quick">
|
||||||
<div class="tab-quick">
|
<div class="tab-quick">
|
||||||
<div class="tab-quick-column">
|
<div class="tab-quick-column">
|
||||||
<button mat-button (click)="applyQuick('BD/P1D', 'today')">
|
<button mat-button (click)="applyQuick('BD/E1D', 'today')">
|
||||||
Today
|
Today
|
||||||
</button>
|
</button>
|
||||||
<button mat-button (click)="applyQuick('B-1D/P1D', 'yesterday')">
|
<button mat-button (click)="applyQuick('B-1D/E-1D', 'yesterday')">
|
||||||
Yesterday
|
Yesterday
|
||||||
</button>
|
</button>
|
||||||
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
|
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
|
||||||
@@ -70,9 +70,6 @@
|
|||||||
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
|
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
|
||||||
This Month
|
This Month
|
||||||
</button>
|
</button>
|
||||||
<button mat-button (click)="applyQuick('BQ/EQ', 'this quarter')">
|
|
||||||
This Quarter
|
|
||||||
</button>
|
|
||||||
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
|
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
|
||||||
This Year
|
This Year
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Validators,
|
Validators,
|
||||||
} from "@angular/forms";
|
} from "@angular/forms";
|
||||||
|
|
||||||
export type DateType = "quick" | "relative" | "absolute";
|
export type DateType = "QUICK" | "RELATIVE" | "ABSOLUTE";
|
||||||
|
|
||||||
export class DateValue {
|
export class DateValue {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -63,7 +63,7 @@ export class DatePickerComponent implements ControlValueAccessor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
datePickerControl = new FormControl(
|
datePickerControl = new FormControl(
|
||||||
new DateValue("quick", "BE/EM", "this month"),
|
new DateValue("QUICK", "BM/EM", "this month"),
|
||||||
);
|
);
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@@ -82,15 +82,19 @@ export class DatePickerComponent implements ControlValueAccessor {
|
|||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
getDateValue(): DateValue {
|
||||||
|
return this.datePickerControl.value!;
|
||||||
|
}
|
||||||
|
|
||||||
writeValue(obj: DateValue): void {
|
writeValue(obj: DateValue): void {
|
||||||
this.datePickerControl.setValue(obj);
|
this.datePickerControl.setValue(obj);
|
||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case "quick":
|
case "QUICK":
|
||||||
break;
|
break;
|
||||||
case "absolute":
|
case "ABSOLUTE":
|
||||||
this.dateRange.setValue(obj.value);
|
this.dateRange.setValue(obj.value);
|
||||||
break;
|
break;
|
||||||
case "relative":
|
case "RELATIVE":
|
||||||
const x = this.relativeTimeRange;
|
const x = this.relativeTimeRange;
|
||||||
// obj.value looks like "P1Y2M3DT4H5M6S" or "PT4H5M6S" or "P1Y2M3D" or "P1YT6S" or ...
|
// obj.value looks like "P1Y2M3DT4H5M6S" or "PT4H5M6S" or "P1Y2M3D" or "P1YT6S" or ...
|
||||||
const matches = obj.value.match(
|
const matches = obj.value.match(
|
||||||
@@ -125,7 +129,7 @@ export class DatePickerComponent implements ControlValueAccessor {
|
|||||||
//(<any> window).initSimpleDatePicker(); // breaks form control
|
//(<any> window).initSimpleDatePicker(); // breaks form control
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDateValue(dateValue: DateValue) {
|
setDateValue(dateValue: DateValue) {
|
||||||
this.datePickerControl.setValue(dateValue);
|
this.datePickerControl.setValue(dateValue);
|
||||||
this._onChange(dateValue);
|
this._onChange(dateValue);
|
||||||
this.dateValueSelected.emit(new DatePickerChange(dateValue));
|
this.dateValueSelected.emit(new DatePickerChange(dateValue));
|
||||||
@@ -133,8 +137,8 @@ export class DatePickerComponent implements ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyQuick(value: string, display: string) {
|
applyQuick(value: string, display: string) {
|
||||||
const newValue = new DateValue("quick", value, display);
|
const newValue = new DateValue("QUICK", value, display);
|
||||||
this._setDateValue(newValue);
|
this.setDateValue(newValue);
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,26 +147,25 @@ export class DatePickerComponent implements ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyRelativeTimeRange() {
|
applyRelativeTimeRange() {
|
||||||
const x = this.relativeTimeRange;
|
|
||||||
const years = x.years ? x.years + "Y" : "";
|
|
||||||
const months = x.months ? x.months + "M" : "";
|
|
||||||
const days = x.days ? x.days + "D" : "";
|
|
||||||
const time = x.hours || x.minutes || x.seconds ? "T" : "";
|
|
||||||
const hours = x.hours ? x.hours + "H" : "";
|
|
||||||
const minutes = x.minutes ? x.minutes + "M" : "";
|
|
||||||
const seconds = x.seconds ? x.seconds + "S" : "";
|
|
||||||
const isoTimeRange =
|
|
||||||
`P${years}${months}${days}${time}${hours}${minutes}${seconds}`;
|
|
||||||
|
|
||||||
const newValue = new DateValue("relative", isoTimeRange, isoTimeRange);
|
const x = this.relativeTimeRange;
|
||||||
this._setDateValue(newValue);
|
const years = x.years ? "-"+x.years + "Y" : "";
|
||||||
|
const months = x.months ? "-"+x.months + "M" : "";
|
||||||
|
const days = x.days ? "-"+x.days + "D" : "";
|
||||||
|
const hours = x.hours ? "-"+x.hours + "H" : "";
|
||||||
|
const minutes = x.minutes ? "-"+x.minutes + "m" : "";
|
||||||
|
|
||||||
|
const timeRange = `B${years}${months}${days}${hours}${minutes}/Bm`;
|
||||||
|
|
||||||
|
const newValue = new DateValue("RELATIVE", timeRange, timeRange);
|
||||||
|
this.setDateValue(newValue);
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAbsoluteTime() {
|
applyAbsoluteTime() {
|
||||||
const value = <string> this.dateRange.value;
|
const value = <string> this.dateRange.value;
|
||||||
const newValue = new DateValue("absolute", value, value);
|
const newValue = new DateValue("ABSOLUTE", value, value);
|
||||||
this._setDateValue(newValue);
|
this.setDateValue(newValue);
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { WidgetDimensions } from '../dashboard.service';
|
|||||||
import { Overlay } from "@angular/cdk/overlay";
|
import { Overlay } from "@angular/cdk/overlay";
|
||||||
|
|
||||||
import { DateTime, Duration } from "luxon";
|
import { DateTime, Duration } from "luxon";
|
||||||
|
import { DateValue } from '../components/datepicker/date-picker.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-plot-view',
|
selector: 'pdb-plot-view',
|
||||||
@@ -32,7 +33,7 @@ export class PlotViewComponent {
|
|||||||
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dateRangeUpdateEvent : EventEmitter<string> = new EventEmitter<string>();
|
dateRangeUpdateEvent : EventEmitter<DateValue> = new EventEmitter<DateValue>();
|
||||||
|
|
||||||
in_drag_mode = false;
|
in_drag_mode = false;
|
||||||
drag_start_x = 0;
|
drag_start_x = 0;
|
||||||
@@ -184,16 +185,16 @@ export class PlotViewComponent {
|
|||||||
const formattedEndDate = endDate.toFormat(this.DATE_PATTERN);
|
const formattedEndDate = endDate.toFormat(this.DATE_PATTERN);
|
||||||
|
|
||||||
const newDateRange = formattedStartDate+" - "+formattedEndDate;
|
const newDateRange = formattedStartDate+" - "+formattedEndDate;
|
||||||
|
const newDateValue = new DateValue('ABSOLUTE', newDateRange, newDateRange);
|
||||||
this.dateRangeUpdateEvent.emit(newDateRange);
|
this.dateRangeUpdateEvent.emit(newDateValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomRange(range: SelectionRange) {
|
zoomRange(range: SelectionRange) {
|
||||||
this.shiftDate(this.config?.dateRange!, range.startPercentOfDateRange, range.endPercentOfDateRange-1);
|
this.shiftDate(this.config?.dateRange.value!, range.startPercentOfDateRange, range.endPercentOfDateRange-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomWithDateAnchor(dateAnchor: DateAnchor){
|
zoomWithDateAnchor(dateAnchor: DateAnchor){
|
||||||
this.shiftDateByAnchor(this.config?.dateRange!, dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor);
|
this.shiftDateByAnchor(this.config?.dateRange.value!, dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomByScroll(event: WheelEvent) {
|
zoomByScroll(event: WheelEvent) {
|
||||||
|
|||||||
@@ -1,72 +1,231 @@
|
|||||||
import { Injectable, OnInit } from '@angular/core';
|
import { Injectable, OnInit } from "@angular/core";
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from "@angular/common/http";
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from "rxjs";
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from "rxjs/operators";
|
||||||
|
import { DateValue } from "./components/datepicker/date-picker.component";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class PlotService {
|
export class PlotService {
|
||||||
|
|
||||||
plotTypes: Array<PlotType>;
|
plotTypes: Array<PlotType>;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
this.plotTypes = new Array<PlotType>();
|
this.plotTypes = new Array<PlotType>();
|
||||||
this.plotTypes.push(new PlotType("SCATTER","Scatter","scatter-chart2",true,DataType.Time,DataType.Duration));
|
this.plotTypes.push(
|
||||||
this.plotTypes.push(new PlotType("CUM_DISTRIBUTION", "Cumulative Distribution", "cumulative-distribution-chart", true, DataType.Percent, DataType.Duration));
|
new PlotType(
|
||||||
this.plotTypes.push(new PlotType("HISTOGRAM", "Histogram", "histogram", true, DataType.HistogramBin, DataType.HistogramCount));
|
"SCATTER",
|
||||||
this.plotTypes.push(new PlotType("PARALLEL", "Parallel Requests", "parallel-requests-chart", true, DataType.Time, DataType.Count));
|
"Scatter",
|
||||||
this.plotTypes.push(new PlotType("BAR", "Bar (number of requests)", "bar-chart", true, DataType.Group, DataType.Count));
|
"scatter-chart2",
|
||||||
this.plotTypes.push(new PlotType("BOX", "Box", "box-plot", true, DataType.Time, DataType.Duration));
|
true,
|
||||||
|
DataType.Time,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"CUM_DISTRIBUTION",
|
||||||
|
"Cumulative Distribution",
|
||||||
|
"cumulative-distribution-chart",
|
||||||
|
true,
|
||||||
|
DataType.Percent,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"HISTOGRAM",
|
||||||
|
"Histogram",
|
||||||
|
"histogram",
|
||||||
|
true,
|
||||||
|
DataType.HistogramBin,
|
||||||
|
DataType.HistogramCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"PARALLEL",
|
||||||
|
"Parallel Requests",
|
||||||
|
"parallel-requests-chart",
|
||||||
|
true,
|
||||||
|
DataType.Time,
|
||||||
|
DataType.Count,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"BAR",
|
||||||
|
"Bar (number of requests)",
|
||||||
|
"bar-chart",
|
||||||
|
true,
|
||||||
|
DataType.Group,
|
||||||
|
DataType.Count,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"BOX",
|
||||||
|
"Box",
|
||||||
|
"box-plot",
|
||||||
|
true,
|
||||||
|
DataType.Time,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
this.plotTypes.push(new PlotType("HEATMAP", "Heatmap", "heatmap", false, DataType.Other, DataType.Other));
|
this.plotTypes.push(
|
||||||
this.plotTypes.push(new PlotType("CONTOUR", "Contour", "contour-chart", false, DataType.Time, DataType.Duration));
|
new PlotType(
|
||||||
this.plotTypes.push(new PlotType("RIDGELINES", "Ridgelines", "ridgelines", false, DataType.Other, DataType.Other));
|
"HEATMAP",
|
||||||
this.plotTypes.push(new PlotType("QQ", "Quantile-Quantile", "quantile-quantile", false, DataType.Other, DataType.Other));
|
"Heatmap",
|
||||||
this.plotTypes.push(new PlotType("VIOLIN", "Violin", "violin-chart", false, DataType.Group, DataType.Duration));
|
"heatmap",
|
||||||
this.plotTypes.push(new PlotType("STRIP", "Strip", "strip-chart", false, DataType.Group, DataType.Duration));
|
false,
|
||||||
this.plotTypes.push(new PlotType("PIE", "Pie", "pie-chart", false, DataType.Other, DataType.Other));
|
DataType.Other,
|
||||||
this.plotTypes.push(new PlotType("STEP_FIT", "Step Fit", "step-fit", false, DataType.Other, DataType.Other));
|
DataType.Other,
|
||||||
this.plotTypes.push(new PlotType("LAG", "Lag", "lag-plot", false, DataType.Other, DataType.Other));
|
),
|
||||||
this.plotTypes.push(new PlotType("ACF", "ACF", "acf-plot", false, DataType.Other, DataType.Other));
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"CONTOUR",
|
||||||
|
"Contour",
|
||||||
|
"contour-chart",
|
||||||
|
false,
|
||||||
|
DataType.Time,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"RIDGELINES",
|
||||||
|
"Ridgelines",
|
||||||
|
"ridgelines",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"QQ",
|
||||||
|
"Quantile-Quantile",
|
||||||
|
"quantile-quantile",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"VIOLIN",
|
||||||
|
"Violin",
|
||||||
|
"violin-chart",
|
||||||
|
false,
|
||||||
|
DataType.Group,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"STRIP",
|
||||||
|
"Strip",
|
||||||
|
"strip-chart",
|
||||||
|
false,
|
||||||
|
DataType.Group,
|
||||||
|
DataType.Duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"PIE",
|
||||||
|
"Pie",
|
||||||
|
"pie-chart",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"STEP_FIT",
|
||||||
|
"Step Fit",
|
||||||
|
"step-fit",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"LAG",
|
||||||
|
"Lag",
|
||||||
|
"lag-plot",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.plotTypes.push(
|
||||||
|
new PlotType(
|
||||||
|
"ACF",
|
||||||
|
"ACF",
|
||||||
|
"acf-plot",
|
||||||
|
false,
|
||||||
|
DataType.Other,
|
||||||
|
DataType.Other,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlotTypes(): Array<PlotType> {
|
getPlotTypes(): Array<PlotType> {
|
||||||
return this.plotTypes.filter(plotType => plotType.active);
|
return this.plotTypes.filter((plotType) => plotType.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTagFields(): Observable<Array<string>> {
|
getTagFields(): Observable<Array<string>> {
|
||||||
return this.http.get<Array<string>>('//'+window.location.hostname+':'+window.location.port+'/api/fields');
|
return this.http.get<Array<string>>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/fields",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable<AutocompleteResult>
|
autocomplete(
|
||||||
{
|
query: string,
|
||||||
|
caretIndex: number,
|
||||||
|
resultMode: ResultMode,
|
||||||
|
): Observable<AutocompleteResult> {
|
||||||
const options = {
|
const options = {
|
||||||
params: new HttpParams()
|
params: new HttpParams()
|
||||||
.set('caretIndex', ""+caretIndex)
|
.set("caretIndex", "" + caretIndex)
|
||||||
.set('query', query)
|
.set("query", query)
|
||||||
.set('resultMode', resultMode)
|
.set("resultMode", resultMode),
|
||||||
};
|
};
|
||||||
return this.http.get<AutocompleteResult>('//'+window.location.hostname+':'+window.location.port+'/api/autocomplete', options);
|
return this.http.get<AutocompleteResult>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/autocomplete",
|
||||||
|
options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
abort(submitterId: string): Observable<void>{
|
abort(submitterId: string): Observable<void> {
|
||||||
return this.http.delete<void>('//'+window.location.hostname+':'+window.location.port+'/api/plots/'+submitterId)
|
return this.http.delete<void>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/plots/" + submitterId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPlotRequest(plotRequest: PlotRequest): Observable<PlotResponse>{
|
sendPlotRequest(plotRequest: PlotRequest): Observable<PlotResponse> {
|
||||||
|
|
||||||
//console.log("send plot request: "+ JSON.stringify(plotRequest));
|
//console.log("send plot request: "+ JSON.stringify(plotRequest));
|
||||||
const result = this.http.post<PlotResponse>('//'+window.location.hostname+':'+window.location.port+'/api/plots', plotRequest);
|
const result = this.http.post<PlotResponse>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/plots",
|
||||||
|
plotRequest,
|
||||||
|
);
|
||||||
return result.pipe(map(this.enrichStats));
|
return result.pipe(map(this.enrichStats));
|
||||||
}
|
}
|
||||||
|
|
||||||
enrichStats(response: PlotResponse): PlotResponse {
|
enrichStats(response: PlotResponse): PlotResponse {
|
||||||
let maxAvgRatio = 0;
|
let maxAvgRatio = 0;
|
||||||
let x : DataSeriesStats[] = response.stats.dataSeriesStats;
|
let x: DataSeriesStats[] = response.stats.dataSeriesStats;
|
||||||
for (const row in x){
|
for (const row in x) {
|
||||||
for (const col in x){
|
for (const col in x) {
|
||||||
maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average);
|
maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,39 +235,42 @@ export class PlotService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterDefaults(): Observable<FilterDefaults>{
|
getFilterDefaults(): Observable<FilterDefaults> {
|
||||||
return this.http.get<FilterDefaults>('//'+window.location.hostname+':'+window.location.port+'/api/filters/defaults')
|
return this.http.get<FilterDefaults>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/filters/defaults",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
splitQuery(query: string, splitBy:string) : Observable<Array<string>>{
|
splitQuery(query: string, splitBy: string): Observable<Array<string>> {
|
||||||
|
const q = "(" + query + ") and " + splitBy + "=";
|
||||||
const q = "("+query+") and "+splitBy+"=";
|
return this.autocomplete(q, q.length + 1, ResultMode.FULL_VALUES).pipe(
|
||||||
return this.autocomplete(q, q.length+1, ResultMode.FULL_VALUES).pipe(
|
|
||||||
map(
|
map(
|
||||||
(autocompleteResult: AutocompleteResult) => autocompleteResult.proposals.map((suggestion:Suggestion) => suggestion.value)
|
(autocompleteResult: AutocompleteResult) =>
|
||||||
)
|
autocompleteResult.proposals.map((suggestion: Suggestion) =>
|
||||||
|
suggestion.value
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class PlotType {
|
export class PlotType {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public id: string,
|
public id: string,
|
||||||
public name: string,
|
public name: string,
|
||||||
public icon: string,
|
public icon: string,
|
||||||
public active: boolean,
|
public active: boolean,
|
||||||
public xAxis: DataType,
|
public xAxis: DataType,
|
||||||
public yAxis: DataType) {
|
public yAxis: DataType,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
compatible(others: Array<PlotType>) : boolean {
|
compatible(others: Array<PlotType>): boolean {
|
||||||
var xAxisTypes = new Set([this.xAxis]);
|
var xAxisTypes = new Set([this.xAxis]);
|
||||||
var yAxisTypes = new Set([this.yAxis]);
|
var yAxisTypes = new Set([this.yAxis]);
|
||||||
|
|
||||||
for(var i = 0; i < others.length; i++){
|
for (var i = 0; i < others.length; i++) {
|
||||||
var other = others[i];
|
var other = others[i];
|
||||||
xAxisTypes.add(other.xAxis);
|
xAxisTypes.add(other.xAxis);
|
||||||
yAxisTypes.add(other.yAxis);
|
yAxisTypes.add(other.yAxis);
|
||||||
@@ -127,40 +289,40 @@ export class TagField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum DataType {
|
export enum DataType {
|
||||||
Time,
|
Time,
|
||||||
Duration,
|
Duration,
|
||||||
Percent,
|
Percent,
|
||||||
Count,
|
Count,
|
||||||
Group,
|
Group,
|
||||||
Metric,
|
Metric,
|
||||||
HistogramBin,
|
HistogramBin,
|
||||||
HistogramCount,
|
HistogramCount,
|
||||||
Other
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AxesTypes {
|
export class AxesTypes {
|
||||||
x : Array<DataType>;
|
x: Array<DataType>;
|
||||||
y : Array<DataType>;
|
y: Array<DataType>;
|
||||||
|
|
||||||
constructor(x: Array<DataType>, y : Array<DataType>) {
|
constructor(x: Array<DataType>, y: Array<DataType>) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasXAxis(type : DataType){
|
hasXAxis(type: DataType) {
|
||||||
return this.x.includes(type);
|
return this.x.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasYAxis(type : DataType){
|
hasYAxis(type: DataType) {
|
||||||
return this.y.includes(type);
|
return this.y.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the 1-indexed axis data type, e.g. getXAxisDataType(1) for the x1 axis
|
* return the 1-indexed axis data type, e.g. getXAxisDataType(1) for the x1 axis
|
||||||
*/
|
*/
|
||||||
getXAxisDataType(index: number){
|
getXAxisDataType(index: number) {
|
||||||
if (this.x.length+1 >= index){
|
if (this.x.length + 1 >= index) {
|
||||||
return this.x[index-1];
|
return this.x[index - 1];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -168,9 +330,9 @@ export class AxesTypes {
|
|||||||
/**
|
/**
|
||||||
* return the 1-indexed axis data type, e.g. getYAxisDataType(1) for the x1 axis
|
* return the 1-indexed axis data type, e.g. getYAxisDataType(1) for the x1 axis
|
||||||
*/
|
*/
|
||||||
getYAxisDataType(index: number){
|
getYAxisDataType(index: number) {
|
||||||
if (this.y.length+1 >= index){
|
if (this.y.length + 1 >= index) {
|
||||||
return this.y[index-1];
|
return this.y[index - 1];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -181,10 +343,10 @@ export class AxesTypes {
|
|||||||
const x2 = this.getXAxisDataType(2);
|
const x2 = this.getXAxisDataType(2);
|
||||||
const y2 = this.getYAxisDataType(2);
|
const y2 = this.getYAxisDataType(2);
|
||||||
|
|
||||||
return (x1 ? "x1:"+DataType[x1] : "")
|
return (x1 ? "x1:" + DataType[x1] : "") +
|
||||||
+ (y1 ? " y1:"+DataType[y1] : "")
|
(y1 ? " y1:" + DataType[y1] : "") +
|
||||||
+ (x2 ? " x2:"+DataType[x2] : "")
|
(x2 ? " x2:" + DataType[x2] : "") +
|
||||||
+ (y2 ? " y2:"+DataType[y2] : "");
|
(y2 ? " y2:" + DataType[y2] : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +354,12 @@ export class Suggestion {
|
|||||||
constructor(
|
constructor(
|
||||||
public value: string,
|
public value: string,
|
||||||
public newQuery: string,
|
public newQuery: string,
|
||||||
public newCaretPosition: number){}
|
public newCaretPosition: number,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AutocompleteResult {
|
||||||
export class AutocompleteResult{
|
constructor(public proposals: Array<Suggestion>) {}
|
||||||
constructor(public proposals: Array<Suggestion>){}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderOptionsMap = {
|
export type RenderOptionsMap = {
|
||||||
@@ -212,9 +374,8 @@ export class PlotRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public submitterId: string,
|
public submitterId: string,
|
||||||
public config: PlotConfig,
|
public config: PlotConfig,
|
||||||
public renders: RenderOptionsMap
|
public renders: RenderOptionsMap,
|
||||||
){}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
copy(): PlotRequest {
|
copy(): PlotRequest {
|
||||||
return JSON.parse(JSON.stringify(this));
|
return JSON.parse(JSON.stringify(this));
|
||||||
@@ -222,17 +383,19 @@ export class PlotRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PlotConfig {
|
export class PlotConfig {
|
||||||
constructor( public query : string,
|
constructor(
|
||||||
public groupBy : Array<string>,
|
public query: string,
|
||||||
public limitBy : string,
|
public groupBy: Array<string>,
|
||||||
public limit : number,
|
public limitBy: string,
|
||||||
public y1:YAxisDefinition,
|
public limit: number,
|
||||||
public y2:YAxisDefinition|undefined,
|
public y1: YAxisDefinition,
|
||||||
public dateRange : string,
|
public y2: YAxisDefinition | undefined,
|
||||||
public aggregates : Array<string>,
|
public dateRange: DateValue,
|
||||||
|
public aggregates: Array<string>,
|
||||||
public intervalUnit: string,
|
public intervalUnit: string,
|
||||||
public intervalValue: number,
|
public intervalValue: number,
|
||||||
public renderBarChartTickLabels: boolean = false,) {}
|
public renderBarChartTickLabels: boolean = false,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RenderOptions {
|
export class RenderOptions {
|
||||||
@@ -240,58 +403,65 @@ export class RenderOptions {
|
|||||||
public height: number,
|
public height: number,
|
||||||
public width: number,
|
public width: number,
|
||||||
public showKey: boolean,
|
public showKey: boolean,
|
||||||
public renderLabels: boolean) {}
|
public renderLabels: boolean,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class YAxisDefinition {
|
export class YAxisDefinition {
|
||||||
constructor(
|
constructor(
|
||||||
public axisScale : string,
|
public axisScale: string,
|
||||||
public rangeMin : number,
|
public rangeMin: number,
|
||||||
public rangeMax : number,
|
public rangeMax: number,
|
||||||
public rangeUnit : string){}
|
public rangeUnit: string,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlotResponse {
|
export class PlotResponse {
|
||||||
constructor(
|
constructor(
|
||||||
public stats : PlotResponseStats,
|
public stats: PlotResponseStats,
|
||||||
public rendered: RenderedImages){}
|
public rendered: RenderedImages,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlotResponseStats {
|
export class PlotResponseStats {
|
||||||
constructor(
|
constructor(
|
||||||
public maxValue : number,
|
public maxValue: number,
|
||||||
public values : number,
|
public values: number,
|
||||||
public average : number,
|
public average: number,
|
||||||
public plottedValues : number,
|
public plottedValues: number,
|
||||||
public maxAvgRatio: number,
|
public maxAvgRatio: number,
|
||||||
public dataSeriesStats : Array<DataSeriesStats>){}
|
public dataSeriesStats: Array<DataSeriesStats>,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataSeriesStats {
|
export class DataSeriesStats {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
public values : number,
|
public values: number,
|
||||||
public maxValue : number,
|
public maxValue: number,
|
||||||
public average : number ,
|
public average: number,
|
||||||
public plottedValues : number,
|
public plottedValues: number,
|
||||||
public dashTypeAndColor: DashTypeAndColor,
|
public dashTypeAndColor: DashTypeAndColor,
|
||||||
public percentiles: Map<string, number>){}
|
public percentiles: Map<string, number>,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashTypeAndColor {
|
export class DashTypeAndColor {
|
||||||
constructor(
|
constructor(
|
||||||
public color: string,
|
public color: string,
|
||||||
public pointType: number) {}
|
public pointType: number,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FilterDefaults {
|
export class FilterDefaults {
|
||||||
constructor(
|
constructor(
|
||||||
public groupBy: Array<string>,
|
public groupBy: Array<string>,
|
||||||
public fields: Array<string>,
|
public fields: Array<string>,
|
||||||
public splitBy: string){}
|
public splitBy: string,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ResultMode {
|
export enum ResultMode {
|
||||||
CUT_AT_DOT = "CUT_AT_DOT",
|
CUT_AT_DOT = "CUT_AT_DOT",
|
||||||
FULL_VALUES = "FULL_VALUES"
|
FULL_VALUES = "FULL_VALUES",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,34 @@
|
|||||||
<div id="visualization">
|
<div id="visualization">
|
||||||
<div id="query-box">
|
<div id="query-box">
|
||||||
<pdb-query-autocomplete #query></pdb-query-autocomplete>
|
<pdb-query-autocomplete #query></pdb-query-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="date-box">
|
<div id="date-box">
|
||||||
|
<app-date-picker #datePicker></app-date-picker>
|
||||||
|
<!--
|
||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Date Range:</mat-label>
|
<mat-label>Date Range:</mat-label>
|
||||||
<input matInput id="search-date-range" value="dateRange" name="dates" />
|
<input matInput id="search-date-range" value="dateRange" name="dates" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="filters">
|
<div id="filters">
|
||||||
<div id="filterpanel">
|
<div id="filterpanel">
|
||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Type:</mat-label>
|
<mat-label>Type:</mat-label>
|
||||||
<mat-select multiple [(ngModel)]="selectedPlotType" (ngModelChange)="changePlotType($event)">
|
<mat-select
|
||||||
<mat-option *ngFor="let plotType of plotTypes" [value]="plotType" [disabled]="!plotType.active">
|
multiple
|
||||||
<img src="assets/img/{{plotType.icon}}.svg" class="icon-select" /> {{plotType.name}}
|
[(ngModel)]="selectedPlotType"
|
||||||
|
(ngModelChange)="changePlotType($event)"
|
||||||
|
>
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let plotType of plotTypes"
|
||||||
|
[value]="plotType"
|
||||||
|
[disabled]="!plotType.active"
|
||||||
|
>
|
||||||
|
<img src="assets/img/{{ plotType.icon }}.svg" class="icon-select" />
|
||||||
|
{{ plotType.name }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@@ -24,60 +36,85 @@
|
|||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Group By:</mat-label>
|
<mat-label>Group By:</mat-label>
|
||||||
<mat-select multiple [(value)]="groupBy">
|
<mat-select multiple [(value)]="groupBy">
|
||||||
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
|
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{
|
||||||
|
tagField.name
|
||||||
|
}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<pdb-limit-by #limitbycomponent></pdb-limit-by>
|
<pdb-limit-by #limitbycomponent></pdb-limit-by>
|
||||||
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
||||||
<mat-form-field >
|
<mat-form-field>
|
||||||
<mat-label>Intervals (only bar chart):</mat-label>
|
<mat-label>Intervals (only bar chart):</mat-label>
|
||||||
<mat-select [(value)]="intervalUnit">
|
<mat-select [(value)]="intervalUnit">
|
||||||
<mat-option value="NO_INTERVAL">-</mat-option>
|
<mat-option value="NO_INTERVAL">-</mat-option>
|
||||||
<mat-option value="SECOND">second</mat-option>
|
<mat-option value="SECOND">second</mat-option>
|
||||||
<mat-option value="MINUTE">minute</mat-option>
|
<mat-option value="MINUTE">minute</mat-option>
|
||||||
<mat-option value="HOUR">hour</mat-option>
|
<mat-option value="HOUR">hour</mat-option>
|
||||||
<mat-option value="DAY">day</mat-option>
|
<mat-option value="DAY">day</mat-option>
|
||||||
<mat-option value="WEEK">week</mat-option>
|
<mat-option value="WEEK">week</mat-option>
|
||||||
<mat-option value="MONTH">month</mat-option>
|
<mat-option value="MONTH">month</mat-option>
|
||||||
<mat-option value="YEAR">year</mat-option>
|
<mat-option value="YEAR">year</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
||||||
<mat-checkbox [(ngModel)]="renderBarChartTickLabels">Show Tic Labels (bar chart)</mat-checkbox>
|
<mat-checkbox [(ngModel)]="renderBarChartTickLabels"
|
||||||
|
>Show Tic Labels (bar chart)</mat-checkbox
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
|
<pdb-y-axis-definition
|
||||||
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
|
#y1AxisDefinitionComponent
|
||||||
|
yIndex="1"
|
||||||
|
></pdb-y-axis-definition>
|
||||||
|
<pdb-y-axis-definition
|
||||||
|
#y2AxisDefinitionComponent
|
||||||
|
yIndex="2"
|
||||||
|
[hidden]="!y2AxisAvailable"
|
||||||
|
></pdb-y-axis-definition>
|
||||||
|
|
||||||
<mat-checkbox *ngIf="galleryEnabled" [(ngModel)]="enableGallery" (click)="toggleGallery($event)">Gallery</mat-checkbox>
|
<mat-checkbox
|
||||||
|
*ngIf="galleryEnabled"
|
||||||
|
[(ngModel)]="enableGallery"
|
||||||
|
(click)="toggleGallery($event)"
|
||||||
|
>Gallery</mat-checkbox
|
||||||
|
>
|
||||||
|
|
||||||
<mat-form-field *ngIf="enableGallery" class="pdb-form-full-width">
|
<mat-form-field *ngIf="enableGallery" class="pdb-form-full-width">
|
||||||
<mat-label>Split By:</mat-label>
|
<mat-label>Split By:</mat-label>
|
||||||
<mat-select [(value)]="splitBy">
|
<mat-select [(value)]="splitBy">
|
||||||
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
|
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{
|
||||||
|
tagField.name
|
||||||
|
}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="splitBy == null || true">
|
<mat-error *ngIf="splitBy == null || true">
|
||||||
Please select a value!
|
Please select a value!
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
|
||||||
<div id="plot-button-bar">
|
<div id="plot-button-bar">
|
||||||
<a
|
<a
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
[routerLink]="['/vis']"
|
[routerLink]="['/vis']"
|
||||||
[queryParams]="{config: serializedConfig()}"
|
[queryParams]="{ config: serializedConfig() }"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="open new window with the same search"
|
aria-label="open new window with the same search"
|
||||||
title="open new window with the same search"><img src="assets/img/link.svg" aria-hidden="true"/></a>
|
title="open new window with the same search"
|
||||||
|
><img src="assets/img/link.svg" aria-hidden="true"
|
||||||
|
/></a>
|
||||||
<button
|
<button
|
||||||
*ngIf="!enableGallery && !plotJobActive"
|
*ngIf="!enableGallery && !plotJobActive"
|
||||||
[disabled]="plotJobActive"
|
[disabled]="plotJobActive"
|
||||||
mat-button
|
mat-button
|
||||||
matTooltip="Create Plot"
|
matTooltip="Create Plot"
|
||||||
(click)="plot()">
|
(click)="plot()"
|
||||||
<img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" />
|
>
|
||||||
|
<img
|
||||||
|
src="assets/img/scatter-chart2.svg"
|
||||||
|
class="icon-inline"
|
||||||
|
aria-hidden="true"
|
||||||
|
title="create plot"
|
||||||
|
/>
|
||||||
Plot
|
Plot
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -85,15 +122,24 @@
|
|||||||
mat-button
|
mat-button
|
||||||
matTooltip="Create Gallery"
|
matTooltip="Create Gallery"
|
||||||
(click)="gallery()"
|
(click)="gallery()"
|
||||||
[disabled]="this.splitBy == null">
|
[disabled]="this.splitBy == null"
|
||||||
<img src="assets/img/four-squares-line.svg" class="icon-inline" aria-hidden="true" title="Create Gallery (only active if 'Split' is set)" />
|
>
|
||||||
|
<img
|
||||||
|
src="assets/img/four-squares-line.svg"
|
||||||
|
class="icon-inline"
|
||||||
|
aria-hidden="true"
|
||||||
|
title="Create Gallery (only active if 'Split' is set)"
|
||||||
|
/>
|
||||||
Gallery
|
Gallery
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="plotJobActive"
|
*ngIf="plotJobActive"
|
||||||
mat-button
|
mat-button
|
||||||
(click)="abort()"
|
(click)="abort()"
|
||||||
matTooltip="abort"><img src="assets/img/close.svg" class="icon-inline" /> Abort</button>
|
matTooltip="abort"
|
||||||
|
>
|
||||||
|
<img src="assets/img/close.svg" class="icon-inline" /> Abort
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,12 +148,8 @@
|
|||||||
<pdb-plot-view
|
<pdb-plot-view
|
||||||
#plotView
|
#plotView
|
||||||
(loadingEvent)="loading($event)"
|
(loadingEvent)="loading($event)"
|
||||||
(dateRangeUpdateEvent)="updateDateRange($event)"></pdb-plot-view>
|
(dateRangeUpdateEvent)="updateDateRange($event)"
|
||||||
<pdb-gallery-view
|
></pdb-plot-view>
|
||||||
#galleryView>
|
<pdb-gallery-view #galleryView> </pdb-gallery-view>
|
||||||
</pdb-gallery-view>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,46 @@
|
|||||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
import {
|
||||||
import { PlotService, PlotType, PlotRequest, TagField, FilterDefaults, DataType, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap, Suggestion } from '../plot.service';
|
AfterViewInit,
|
||||||
import { UntypedFormControl, } from '@angular/forms';
|
Component,
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
Input,
|
||||||
import { LimitByComponent } from '../limit-by/limit-by.component';
|
OnInit,
|
||||||
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
|
ViewChild,
|
||||||
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
|
} from "@angular/core";
|
||||||
import { PlotViewComponent, LoadingEvent } from '../plot-view/plot-view.component';
|
import {
|
||||||
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
|
AxesTypes,
|
||||||
import { WidgetDimensions } from '../dashboard.service';
|
DataType,
|
||||||
|
FilterDefaults,
|
||||||
|
PlotConfig,
|
||||||
|
PlotRequest,
|
||||||
|
PlotService,
|
||||||
|
PlotType,
|
||||||
|
RenderOptions,
|
||||||
|
RenderOptionsMap,
|
||||||
|
Suggestion,
|
||||||
|
TagField,
|
||||||
|
} from "../plot.service";
|
||||||
|
import { UntypedFormControl } from "@angular/forms";
|
||||||
|
import { MatSnackBar } from "@angular/material/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 {
|
||||||
|
DateRange,
|
||||||
|
LoadingEvent,
|
||||||
|
PlotViewComponent,
|
||||||
|
} from "../plot-view/plot-view.component";
|
||||||
|
import { GalleryViewComponent } from "../gallery-view/gallery-view.component";
|
||||||
|
import { WidgetDimensions } from "../dashboard.service";
|
||||||
|
import {
|
||||||
|
DatePickerComponent,
|
||||||
|
DateValue,
|
||||||
|
} from "../components/datepicker/date-picker.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-visualization-page',
|
selector: "pdb-visualization-page",
|
||||||
templateUrl: './visualization-page.component.html',
|
templateUrl: "./visualization-page.component.html",
|
||||||
styleUrls: ['./visualization-page.component.scss']
|
styleUrls: ["./visualization-page.component.scss"],
|
||||||
})
|
})
|
||||||
export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@@ -24,7 +49,9 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
|||||||
@Input()
|
@Input()
|
||||||
galleryEnabled = true;
|
galleryEnabled = true;
|
||||||
|
|
||||||
dateRange = new UntypedFormControl('2019-10-05 00:00:00 - 2019-10-11 23:59:59');
|
dateRange = new UntypedFormControl(
|
||||||
|
"2019-10-05 00:00:00 - 2019-10-11 23:59:59",
|
||||||
|
);
|
||||||
|
|
||||||
selectedPlotType = new Array<PlotType>();
|
selectedPlotType = new Array<PlotType>();
|
||||||
plotTypes: PlotType[] = [];
|
plotTypes: PlotType[] = [];
|
||||||
@@ -33,37 +60,38 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
groupBy = new Array<TagField>();
|
groupBy = new Array<TagField>();
|
||||||
|
|
||||||
@ViewChild('limitbycomponent')
|
@ViewChild("limitbycomponent")
|
||||||
private limitbycomponent! : LimitByComponent;
|
private limitbycomponent!: LimitByComponent;
|
||||||
|
|
||||||
|
@ViewChild("y1AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||||
|
private y1AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||||
|
|
||||||
@ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
@ViewChild("y2AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||||
private y1AxisDefinitionComponent! : YAxisDefinitionComponent;
|
private y2AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||||
|
|
||||||
@ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
@ViewChild("query")
|
||||||
private y2AxisDefinitionComponent! : YAxisDefinitionComponent;
|
|
||||||
|
|
||||||
@ViewChild('query')
|
|
||||||
query!: QueryAutocompleteComponent;
|
query!: QueryAutocompleteComponent;
|
||||||
|
|
||||||
@ViewChild('plotView')
|
@ViewChild("plotView")
|
||||||
plotView!: PlotViewComponent;
|
plotView!: PlotViewComponent;
|
||||||
|
|
||||||
@ViewChild('galleryView')
|
@ViewChild("galleryView")
|
||||||
galleryView!: GalleryViewComponent;
|
galleryView!: GalleryViewComponent;
|
||||||
|
|
||||||
|
@ViewChild("datePicker")
|
||||||
|
datePicker!: DatePickerComponent;
|
||||||
|
|
||||||
enableGallery = false;
|
enableGallery = false;
|
||||||
splitBy : TagField | undefined = undefined;
|
splitBy: TagField | undefined = undefined;
|
||||||
y2AxisAvailable = false;
|
y2AxisAvailable = false;
|
||||||
|
|
||||||
intervalUnit = 'NO_INTERVAL';
|
intervalUnit = "NO_INTERVAL";
|
||||||
intervalValue = 1;
|
intervalValue = 1;
|
||||||
renderBarChartTickLabels = false;
|
renderBarChartTickLabels = false;
|
||||||
|
|
||||||
plotJobActive = false;
|
plotJobActive = false;
|
||||||
|
|
||||||
constructor(private plotService: PlotService, private snackBar: MatSnackBar) {
|
constructor(private plotService: PlotService, private snackBar: MatSnackBar) {
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (!this.defaultConfig && params.get("config")) {
|
if (!this.defaultConfig && params.get("config")) {
|
||||||
const config = JSON.parse(params.get("config")!);
|
const config = JSON.parse(params.get("config")!);
|
||||||
@@ -71,105 +99,116 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(message:string) {
|
showError(message: string) {
|
||||||
this.snackBar.open(message, "", {
|
this.snackBar.open(message, "", {
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
verticalPosition: 'top'
|
verticalPosition: "top",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
(<any> window).initDatePicker();
|
||||||
(<any>window).initDatePicker();
|
|
||||||
|
|
||||||
this.plotTypes = this.plotService.getPlotTypes();
|
this.plotTypes = this.plotService.getPlotTypes();
|
||||||
this.selectedPlotType.push(this.plotTypes[0]);
|
this.selectedPlotType.push(this.plotTypes[0]);
|
||||||
|
|
||||||
this.plotService.getFilterDefaults().subscribe((filterDefaults: FilterDefaults) => {
|
this.plotService.getFilterDefaults().subscribe(
|
||||||
|
(filterDefaults: FilterDefaults) => {
|
||||||
|
filterDefaults.fields.forEach((name: string) => {
|
||||||
|
this.tagFields.push(new TagField(name));
|
||||||
|
}, (error: any) => {
|
||||||
|
this.showError(error.error.message);
|
||||||
|
});
|
||||||
|
|
||||||
filterDefaults.fields.forEach((name:string) => {
|
const groupByDefaults = this.defaultConfig
|
||||||
this.tagFields.push(new TagField(name));
|
? this.defaultConfig.groupBy
|
||||||
|
: filterDefaults.groupBy;
|
||||||
|
this.groupBy = this.tagFields.filter((val) =>
|
||||||
|
groupByDefaults.includes(val.name)
|
||||||
|
);
|
||||||
|
this.splitBy = this.tagFields.find((val) =>
|
||||||
|
filterDefaults.splitBy == val.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.defaultConfig) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(error: any) => {
|
);
|
||||||
this.showError(error.error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupByDefaults = this.defaultConfig ? this.defaultConfig.groupBy : filterDefaults.groupBy;
|
|
||||||
this.groupBy = this.tagFields.filter(val => groupByDefaults.includes(val.name));
|
|
||||||
this.splitBy = this.tagFields.find(val => filterDefaults.splitBy == val.name);
|
|
||||||
|
|
||||||
if (this.defaultConfig) {
|
|
||||||
this.plot();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (this.defaultConfig) {
|
if (this.defaultConfig) {
|
||||||
const c = this.defaultConfig;
|
const c = this.defaultConfig;
|
||||||
this.query.suggestionFetcherEnabled = false;
|
this.query.suggestionFetcherEnabled = false;
|
||||||
this.query.queryField.setValue(new Suggestion(c.query, c.query, c.query.length));
|
this.query.queryField.setValue(
|
||||||
this.query.suggestionFetcherEnabled = true;
|
new Suggestion(c.query, c.query, c.query.length),
|
||||||
|
);
|
||||||
|
this.query.suggestionFetcherEnabled = true;
|
||||||
|
|
||||||
this.selectedPlotType = this.plotTypes.filter(pt => c.aggregates.includes(pt.id));
|
this.selectedPlotType = this.plotTypes.filter((pt) =>
|
||||||
this.changePlotType(this.selectedPlotType);
|
c.aggregates.includes(pt.id)
|
||||||
this.updateDateRange(c.dateRange, false);
|
);
|
||||||
this.limitbycomponent.limitBy = c.limitBy;
|
this.changePlotType(this.selectedPlotType);
|
||||||
this.limitbycomponent.limit = c.limit;
|
this.updateDateRange(c.dateRange, false);
|
||||||
this.intervalUnit = c.intervalUnit;
|
this.limitbycomponent.limitBy = c.limitBy;
|
||||||
this.intervalValue = c.intervalValue;
|
this.limitbycomponent.limit = c.limit;
|
||||||
this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale;
|
this.intervalUnit = c.intervalUnit;
|
||||||
this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin;
|
this.intervalValue = c.intervalValue;
|
||||||
this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax;
|
this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale;
|
||||||
this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit;
|
this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin;
|
||||||
|
this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax;
|
||||||
|
this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit;
|
||||||
|
|
||||||
if (c.y2) {
|
if (c.y2) {
|
||||||
this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale;
|
this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale;
|
||||||
this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin;
|
this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin;
|
||||||
this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax;
|
this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax;
|
||||||
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toggleGallery(event: Event){
|
toggleGallery(event: Event) {
|
||||||
this.galleryView.show = this.enableGallery;
|
this.galleryView.show = this.enableGallery;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading(event: LoadingEvent) {
|
loading(event: LoadingEvent) {
|
||||||
this.plotJobActive = event.loading;
|
this.plotJobActive = event.loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDateRange(newDateRange: string, updatePlot=true) {
|
updateDateRange(newDateRange: DateValue, updatePlot = true) {
|
||||||
(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
|
this.datePicker.setDateValue(newDateRange);
|
||||||
if (updatePlot){
|
if (updatePlot) {
|
||||||
this.plot();
|
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) =>
|
||||||
this.plotTypes.forEach(pt => pt.active=false);
|
pt.compatible(selectedPlotTypes)
|
||||||
compatiblePlotTypes.forEach(pt => pt.active=true);
|
);
|
||||||
|
this.plotTypes.forEach((pt) => pt.active = false);
|
||||||
|
compatiblePlotTypes.forEach((pt) => pt.active = true);
|
||||||
|
|
||||||
const axesTypes = this.getAxes();
|
const axesTypes = this.getAxes();
|
||||||
this.y2AxisAvailable = axesTypes.y.length == 2;
|
this.y2AxisAvailable = axesTypes.y.length == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedPlotTypesContains(plotTypeIds: Array<string>){
|
selectedPlotTypesContains(plotTypeIds: Array<string>) {
|
||||||
return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0;
|
return this.selectedPlotType.filter((pt) => plotTypeIds.includes(pt.id))
|
||||||
|
.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dateRangeAsString(): DateValue {
|
||||||
dateRangeAsString() : string {
|
return this.datePicker.getDateValue();
|
||||||
return (<HTMLInputElement>document.getElementById("search-date-range")).value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery(){
|
gallery() {
|
||||||
if (this.splitBy != null){
|
if (this.splitBy != null) {
|
||||||
this.plotView.imageUrl = '';
|
this.plotView.imageUrl = "";
|
||||||
this.plotView.stats = null;
|
this.plotView.stats = null;
|
||||||
this.galleryView.show=true;
|
this.galleryView.show = true;
|
||||||
const request = this.createPlotRequest();
|
const request = this.createPlotRequest();
|
||||||
this.galleryView.renderGallery(request, this.splitBy.name);
|
this.galleryView.renderGallery(request, this.splitBy.name);
|
||||||
} else {
|
} else {
|
||||||
@@ -177,12 +216,11 @@ toggleGallery(event: Event){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAxes() : AxesTypes {
|
getAxes(): AxesTypes {
|
||||||
|
|
||||||
const x = new Array<DataType>();
|
const x = new Array<DataType>();
|
||||||
const y = new Array<DataType>();
|
const y = new Array<DataType>();
|
||||||
|
|
||||||
for(var i = 0; i < this.selectedPlotType.length; i++){
|
for (var i = 0; i < this.selectedPlotType.length; i++) {
|
||||||
var plotType = this.selectedPlotType[i];
|
var plotType = this.selectedPlotType[i];
|
||||||
if (!x.includes(plotType.xAxis)) {
|
if (!x.includes(plotType.xAxis)) {
|
||||||
x.push(plotType.xAxis);
|
x.push(plotType.xAxis);
|
||||||
@@ -192,43 +230,46 @@ toggleGallery(event: Event){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AxesTypes(x,y);
|
return new AxesTypes(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
this.plotService.abort((<any>window).submitterId).subscribe({
|
this.plotService.abort((<any> window).submitterId).subscribe({
|
||||||
complete: () => {
|
complete: () => {
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
plot(){
|
plot() {
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
this.plotView.plot(config, this.plotDimensionSupplier);
|
this.plotView.plot(config, this.plotDimensionSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
plotDimensionSupplier(): WidgetDimensions{
|
plotDimensionSupplier(): WidgetDimensions {
|
||||||
const results = document.getElementById("results");
|
const results = document.getElementById("results");
|
||||||
return new WidgetDimensions(
|
return new WidgetDimensions(
|
||||||
results != null ? results.offsetWidth-1 : 1024,
|
results != null ? results.offsetWidth - 1 : 1024,
|
||||||
results != null ? results.offsetHeight-1: 1024);
|
results != null ? results.offsetHeight - 1 : 1024,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlotConfig(): PlotConfig {
|
createPlotConfig(): PlotConfig {
|
||||||
const aggregates = new Array<string>();
|
const aggregates = new Array<string>();
|
||||||
this.selectedPlotType.forEach(a => aggregates.push(a.id));
|
this.selectedPlotType.forEach((a) => aggregates.push(a.id));
|
||||||
|
|
||||||
const y1 = this.y1AxisDefinitionComponent.getAxisDefinition();
|
const y1 = this.y1AxisDefinitionComponent.getAxisDefinition();
|
||||||
const y2 = this.y2AxisDefinitionComponent ? this.y2AxisDefinitionComponent.getAxisDefinition() : undefined;
|
const y2 = this.y2AxisDefinitionComponent
|
||||||
|
? this.y2AxisDefinitionComponent.getAxisDefinition()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const config = new PlotConfig(
|
const config = new PlotConfig(
|
||||||
this.query.query,
|
this.query.query,
|
||||||
this.groupBy.map(o => o.name),
|
this.groupBy.map((o) => o.name),
|
||||||
this.limitbycomponent.limitBy,
|
this.limitbycomponent.limitBy,
|
||||||
this.limitbycomponent.limit,
|
this.limitbycomponent.limit,
|
||||||
y1,
|
y1,
|
||||||
y2,
|
y2,
|
||||||
this.dateRangeAsString(), // dateRange
|
this.datePicker.getDateValue(), // dateRange
|
||||||
aggregates, // aggregates
|
aggregates, // aggregates
|
||||||
this.intervalUnit,
|
this.intervalUnit,
|
||||||
this.intervalValue,
|
this.intervalValue,
|
||||||
@@ -242,24 +283,29 @@ toggleGallery(event: Event){
|
|||||||
|
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
|
|
||||||
const renderOptions : RenderOptionsMap = {
|
const renderOptions: RenderOptionsMap = {
|
||||||
'main': new RenderOptions(results!.offsetHeight-1, results!.offsetWidth-1, true, true),
|
"main": new RenderOptions(
|
||||||
'thumbnail': new RenderOptions(200, 300, false, false),
|
results!.offsetHeight - 1,
|
||||||
|
results!.offsetWidth - 1,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
"thumbnail": new RenderOptions(200, 300, false, false),
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = new PlotRequest(
|
const request = new PlotRequest(
|
||||||
(<any>window).submitterId,
|
(<any> window).submitterId,
|
||||||
config,
|
config,
|
||||||
renderOptions
|
renderOptions,
|
||||||
);
|
);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedConfig(): string {
|
serializedConfig(): string {
|
||||||
try{
|
try {
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
return JSON.stringify(config);
|
return JSON.stringify(config);
|
||||||
}catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,78 +25,98 @@ public class DateTimeRangeParser {
|
|||||||
|
|
||||||
private static OffsetDateTime parseInternal(final OffsetDateTime offsetTime, final String timeDefinition) {
|
private static OffsetDateTime parseInternal(final OffsetDateTime offsetTime, final String timeDefinition) {
|
||||||
|
|
||||||
final Pattern regex = Pattern.compile("(?<beginEnd>[BE]?)(?<amount>\\-?[0-9]*)(?<unit>[mHDWMY])");
|
final Pattern regex = Pattern.compile("(?<beginEnd>[BE])(?<amountUnit>(\\-?[0-9]*[mHDWMY])+)",
|
||||||
|
Pattern.MULTILINE);
|
||||||
|
|
||||||
final Matcher matcher = regex.matcher(timeDefinition);
|
final Matcher matcher = regex.matcher(timeDefinition);
|
||||||
|
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
|
|
||||||
final String beginEnd = matcher.group("beginEnd");
|
final String beginEnd = matcher.group("beginEnd");
|
||||||
final boolean begin = "B".equals(beginEnd);
|
final boolean begin = "B".equals(beginEnd);
|
||||||
final String amountString = matcher.group("amount");
|
|
||||||
final int amount = amountString.equals("") ? 0 : Integer.parseInt(amountString);
|
|
||||||
final String unitString = matcher.group("unit");
|
|
||||||
|
|
||||||
switch (unitString) {
|
OffsetDateTime result = offsetTime;
|
||||||
case "m": {
|
|
||||||
final ChronoUnit unit = ChronoUnit.MINUTES;
|
final String amountUnitString = matcher.group("amountUnit");
|
||||||
if (begin) {
|
final Pattern regexAmountUnit = Pattern.compile("(?<amount>\\-?[0-9]*)(?<unit>[mHDWMY])");
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
final Matcher m = regexAmountUnit.matcher(amountUnitString);
|
||||||
} else {
|
while (m.find()) {
|
||||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
final String amountString = m.group("amount");
|
||||||
|
final String unitString = m.group("unit");
|
||||||
|
final int amount = amountString.equals("") ? 0 : Integer.parseInt(amountString);
|
||||||
|
|
||||||
|
switch (unitString) {
|
||||||
|
case "m": {
|
||||||
|
final ChronoUnit unit = ChronoUnit.MINUTES;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(unit);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "H": {
|
||||||
|
final ChronoUnit unit = ChronoUnit.HOURS;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(unit);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "D": {
|
||||||
|
final ChronoUnit unit = ChronoUnit.DAYS;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(unit);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "W": {
|
||||||
|
final DayOfWeek firstDayOfWeek = DayOfWeek.MONDAY;
|
||||||
|
final DayOfWeek lastDayOfWeek = DayOfWeek
|
||||||
|
.of(((firstDayOfWeek.getValue() - 1 + 6) % DayOfWeek.values().length) + 1); // weird
|
||||||
|
// computation,
|
||||||
|
// because
|
||||||
|
// DayOfWeek
|
||||||
|
// goes from 1
|
||||||
|
// to 7
|
||||||
|
final ChronoUnit unit = ChronoUnit.WEEKS;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
|
||||||
|
.truncatedTo(ChronoUnit.DAYS);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount, unit).with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
|
||||||
|
.plus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "M": {
|
||||||
|
final ChronoUnit unit = ChronoUnit.MONTHS;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1)
|
||||||
|
.plus(1, ChronoUnit.MONTHS).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Y": {
|
||||||
|
final ChronoUnit unit = ChronoUnit.YEARS;
|
||||||
|
if (begin) {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
|
||||||
|
} else {
|
||||||
|
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1)
|
||||||
|
.plus(1, ChronoUnit.YEARS).minusSeconds(1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unexpected value: " + unitString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "H": {
|
return result;
|
||||||
final ChronoUnit unit = ChronoUnit.HOURS;
|
|
||||||
if (begin) {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
|
||||||
} else {
|
|
||||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "D": {
|
|
||||||
final ChronoUnit unit = ChronoUnit.DAYS;
|
|
||||||
if (begin) {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
|
||||||
} else {
|
|
||||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "W": {
|
|
||||||
final DayOfWeek firstDayOfWeek = DayOfWeek.MONDAY;
|
|
||||||
final DayOfWeek lastDayOfWeek = DayOfWeek
|
|
||||||
.of(((firstDayOfWeek.getValue() - 1 + 6) % DayOfWeek.values().length) + 1); // weird
|
|
||||||
// computation,
|
|
||||||
// because DayOfWeek
|
|
||||||
// goes from 1 to 7
|
|
||||||
final ChronoUnit unit = ChronoUnit.WEEKS;
|
|
||||||
if (begin) {
|
|
||||||
return offsetTime.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
|
|
||||||
.truncatedTo(ChronoUnit.DAYS);
|
|
||||||
} else {
|
|
||||||
return offsetTime.plus(amount, unit).with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
|
|
||||||
.plus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS).minusSeconds(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "M": {
|
|
||||||
final ChronoUnit unit = ChronoUnit.MONTHS;
|
|
||||||
if (begin) {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
|
|
||||||
} else {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1)
|
|
||||||
.plus(1, ChronoUnit.MONTHS).minusSeconds(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "Y": {
|
|
||||||
final ChronoUnit unit = ChronoUnit.YEARS;
|
|
||||||
if (begin) {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
|
|
||||||
} else {
|
|
||||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1)
|
|
||||||
.plus(1, ChronoUnit.YEARS).minusSeconds(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unexpected value: " + unitString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("invalid input: " + timeDefinition);
|
throw new IllegalArgumentException("invalid input: " + timeDefinition);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.lucares.pdb.plot.api;
|
||||||
|
|
||||||
|
public class DateValue {
|
||||||
|
public enum DateType {
|
||||||
|
QUICK, RELATIVE, ABSOLUTE
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateType type;
|
||||||
|
|
||||||
|
private String display;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public DateType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(final DateType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplay() {
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplay(final String display) {
|
||||||
|
this.display = display;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(final String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import org.lucares.utils.Preconditions;
|
|||||||
|
|
||||||
public class PlotSettings {
|
public class PlotSettings {
|
||||||
|
|
||||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
private String query;
|
private String query;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ public class PlotSettings {
|
|||||||
|
|
||||||
private int limit;
|
private int limit;
|
||||||
|
|
||||||
private String dateRangeAsString;
|
private DateValue dateValue;
|
||||||
|
|
||||||
private YAxisDefinition y1;
|
private YAxisDefinition y1;
|
||||||
private YAxisDefinition y2;
|
private YAxisDefinition y2;
|
||||||
@@ -80,30 +80,37 @@ public class PlotSettings {
|
|||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDateRange() {
|
public DateValue getDateRange() {
|
||||||
return dateRangeAsString;
|
return dateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDateRange(final String dateRangeAsString) {
|
public void setDateRange(final DateValue dateValue) {
|
||||||
this.dateRangeAsString = dateRangeAsString;
|
this.dateValue = dateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeRange dateRange() {
|
public DateTimeRange dateRange() {
|
||||||
|
|
||||||
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
|
switch (this.dateValue.getType()) {
|
||||||
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
|
case RELATIVE:
|
||||||
|
case QUICK:
|
||||||
|
final DateTimeRange dateTimeRange = DateTimeRangeParser.parse(OffsetDateTime.now(), dateValue.getValue());
|
||||||
|
return dateTimeRange;
|
||||||
|
case ABSOLUTE:
|
||||||
|
final String[] startEnd = dateValue.getValue().split(Pattern.quote(" - "));
|
||||||
|
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateValue);
|
||||||
|
|
||||||
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
||||||
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
||||||
|
return new DateTimeRange(startDate, endDate);
|
||||||
return new DateTimeRange(startDate, endDate);
|
}
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PlotSettings [query=" + query + ", groupBy=" + groupBy + ", limitBy=" + limitBy + ", limit=" + limit
|
return "PlotSettings [query=" + query + ", groupBy=" + groupBy + ", limitBy=" + limitBy + ", limit=" + limit
|
||||||
+ ", dateRangeAsString=" + dateRangeAsString + ", y1=" + y1 + " y2=" + y2 + ", aggregates=" + aggregates
|
+ ", dateRangeAsString=" + dateValue + ", y1=" + y1 + " y2=" + y2 + ", aggregates=" + aggregates
|
||||||
+ ", renders=" + renders + "]";
|
+ ", renders=" + renders + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,11 +81,10 @@ public class DateTimeRangeParserTest {
|
|||||||
Arguments.of("2024-03-29 12:00:00", "BY/EY", "2024-01-01 00:00:00", "2024-12-31 23:59:59"),
|
Arguments.of("2024-03-29 12:00:00", "BY/EY", "2024-01-01 00:00:00", "2024-12-31 23:59:59"),
|
||||||
|
|
||||||
// previous year
|
// previous year
|
||||||
Arguments.of("2024-03-29 12:00:00", "B-1Y/E-1Y", "2023-01-01 00:00:00", "2023-12-31 23:59:59")
|
Arguments.of("2024-03-29 12:00:00", "B-1Y/E-1Y", "2023-01-01 00:00:00", "2023-12-31 23:59:59"),
|
||||||
|
|
||||||
//
|
//
|
||||||
// Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00",
|
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", "2024-03-29 12:00:00")
|
||||||
// "2024-03-29 12:00:00")
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -104,4 +103,31 @@ public class DateTimeRangeParserTest {
|
|||||||
Assertions.assertEquals(expectedStart, actualStart, "start");
|
Assertions.assertEquals(expectedStart, actualStart, "start");
|
||||||
Assertions.assertEquals(expectedEnd, actualEnd, "end");
|
Assertions.assertEquals(expectedEnd, actualEnd, "end");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Stream<Arguments> providerDatePeriods_multiple() {
|
||||||
|
|
||||||
|
return Stream.of(//
|
||||||
|
|
||||||
|
//
|
||||||
|
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", "2024-03-29 12:00:00"),
|
||||||
|
|
||||||
|
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/EH15m", "2024-03-29 10:45:00", "2024-03-29 13:14:59")
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("providerDatePeriods_multiple")
|
||||||
|
public void testDatePeriods_multiple(final String now, final String datePeriod, final String expectedStart,
|
||||||
|
final String expectedEnd) throws Exception {
|
||||||
|
final OffsetDateTime offsetTime = LocalDateTime.parse(now, PlotSettings.DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
||||||
|
|
||||||
|
final DateTimeRange actual = DateTimeRangeParser.parse(offsetTime, datePeriod);
|
||||||
|
|
||||||
|
final String actualStart = PlotSettings.DATE_FORMAT.format(actual.getStart());
|
||||||
|
final String actualEnd = PlotSettings.DATE_FORMAT.format(actual.getEnd());
|
||||||
|
System.out.println("at " + now + " " + datePeriod + " -> " + actualStart + " - " + actualEnd);
|
||||||
|
Assertions.assertEquals(expectedStart, actualStart, "start");
|
||||||
|
Assertions.assertEquals(expectedEnd, actualEnd, "end");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.lucares.pdb.plot.api.Aggregate;
|
import org.lucares.pdb.plot.api.Aggregate;
|
||||||
|
import org.lucares.pdb.plot.api.DateValue;
|
||||||
import org.lucares.pdb.plot.api.Limit;
|
import org.lucares.pdb.plot.api.Limit;
|
||||||
import org.lucares.pdb.plot.api.YAxisDefinition;
|
import org.lucares.pdb.plot.api.YAxisDefinition;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ public class PlotConfig {
|
|||||||
private YAxisDefinition y1 = new YAxisDefinition();
|
private YAxisDefinition y1 = new YAxisDefinition();
|
||||||
private YAxisDefinition y2 = new YAxisDefinition();
|
private YAxisDefinition y2 = new YAxisDefinition();
|
||||||
|
|
||||||
private String dateRange;
|
private DateValue dateRange;
|
||||||
|
|
||||||
private List<Aggregate> aggregates = new ArrayList<>();
|
private List<Aggregate> aggregates = new ArrayList<>();
|
||||||
|
|
||||||
@@ -66,11 +67,11 @@ public class PlotConfig {
|
|||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDateRange() {
|
public DateValue getDateRange() {
|
||||||
return dateRange;
|
return dateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDateRange(final String dateRange) {
|
public void setDateRange(final DateValue dateRange) {
|
||||||
this.dateRange = dateRange;
|
this.dateRange = dateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user