From 21a84b52233f43e8dee210d6bb13f07c14ab2cea Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sun, 5 May 2024 10:22:45 +0200 Subject: [PATCH] use date picker in visualization page --- .../date-picker-test.component.html | 16 +- .../datepicker/date-picker-test.component.ts | 27 +- .../datepicker/date-picker.component.html | 7 +- .../datepicker/date-picker.component.ts | 47 +- .../src/app/plot-view/plot-view.component.ts | 11 +- pdb-js/src/app/plot.service.ts | 480 ++++++++++++------ .../visualization-page.component.html | 156 +++--- .../visualization-page.component.ts | 330 ++++++------ .../pdb/plot/api/DateTimeRangeParser.java | 148 +++--- .../org/lucares/pdb/plot/api/DateValue.java | 38 ++ .../lucares/pdb/plot/api/PlotSettings.java | 33 +- .../pdb/plot/api/DateTimeRangeParserTest.java | 34 +- .../org/lucares/pdbui/domain/PlotConfig.java | 7 +- 13 files changed, 856 insertions(+), 478 deletions(-) create mode 100644 pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateValue.java diff --git a/pdb-js/src/app/components/datepicker/date-picker-test.component.html b/pdb-js/src/app/components/datepicker/date-picker-test.component.html index 44e2b0d..28149d5 100644 --- a/pdb-js/src/app/components/datepicker/date-picker-test.component.html +++ b/pdb-js/src/app/components/datepicker/date-picker-test.component.html @@ -1,5 +1,15 @@ - - +
+ - \ No newline at end of file + Type: {{ datePicker.value?.type }}
+ value: {{ datePicker.value?.value }}
+ display: {{ datePicker.value?.display }}
+
+ + +
{{ output }}
diff --git a/pdb-js/src/app/components/datepicker/date-picker-test.component.ts b/pdb-js/src/app/components/datepicker/date-picker-test.component.ts index 8aa8b5d..39fd4bb 100644 --- a/pdb-js/src/app/components/datepicker/date-picker-test.component.ts +++ b/pdb-js/src/app/components/datepicker/date-picker-test.component.ts @@ -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({ - selector: 'app-date-picker-test', - templateUrl: './date-picker-test.component.html' + selector: "app-date-picker-test", + templateUrl: "./date-picker-test.component.html", }) export class DatePickerTestComponent { - - constructor(){ + datePicker = new FormControl( + 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", + ), + ); + + output = ""; + + readValue() { + this.output = this.datePicker.value?.type + " " + + this.datePicker.value?.value; } + dateChanged(e: any) { + console.log("dateChanged", e); + this.output += "dateChanged: " + e; + } } diff --git a/pdb-js/src/app/components/datepicker/date-picker.component.html b/pdb-js/src/app/components/datepicker/date-picker.component.html index 019b38f..a96acd1 100644 --- a/pdb-js/src/app/components/datepicker/date-picker.component.html +++ b/pdb-js/src/app/components/datepicker/date-picker.component.html @@ -58,10 +58,10 @@
- - - diff --git a/pdb-js/src/app/components/datepicker/date-picker.component.ts b/pdb-js/src/app/components/datepicker/date-picker.component.ts index 256798a..60b4caf 100644 --- a/pdb-js/src/app/components/datepicker/date-picker.component.ts +++ b/pdb-js/src/app/components/datepicker/date-picker.component.ts @@ -12,7 +12,7 @@ import { Validators, } from "@angular/forms"; -export type DateType = "quick" | "relative" | "absolute"; +export type DateType = "QUICK" | "RELATIVE" | "ABSOLUTE"; export class DateValue { constructor( @@ -63,7 +63,7 @@ export class DatePickerComponent implements ControlValueAccessor { ); datePickerControl = new FormControl( - new DateValue("quick", "BE/EM", "this month"), + new DateValue("QUICK", "BM/EM", "this month"), ); @Input() @@ -82,15 +82,19 @@ export class DatePickerComponent implements ControlValueAccessor { constructor() {} + getDateValue(): DateValue { + return this.datePickerControl.value!; + } + writeValue(obj: DateValue): void { this.datePickerControl.setValue(obj); switch (obj.type) { - case "quick": + case "QUICK": break; - case "absolute": + case "ABSOLUTE": this.dateRange.setValue(obj.value); break; - case "relative": + case "RELATIVE": const x = this.relativeTimeRange; // obj.value looks like "P1Y2M3DT4H5M6S" or "PT4H5M6S" or "P1Y2M3D" or "P1YT6S" or ... const matches = obj.value.match( @@ -125,7 +129,7 @@ export class DatePickerComponent implements ControlValueAccessor { //( window).initSimpleDatePicker(); // breaks form control } - _setDateValue(dateValue: DateValue) { + setDateValue(dateValue: DateValue) { this.datePickerControl.setValue(dateValue); this._onChange(dateValue); this.dateValueSelected.emit(new DatePickerChange(dateValue)); @@ -133,8 +137,8 @@ export class DatePickerComponent implements ControlValueAccessor { } applyQuick(value: string, display: string) { - const newValue = new DateValue("quick", value, display); - this._setDateValue(newValue); + const newValue = new DateValue("QUICK", value, display); + this.setDateValue(newValue); this.isOpen = false; } @@ -143,26 +147,25 @@ export class DatePickerComponent implements ControlValueAccessor { } 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); - this._setDateValue(newValue); + 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 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; } applyAbsoluteTime() { const value = this.dateRange.value; - const newValue = new DateValue("absolute", value, value); - this._setDateValue(newValue); + const newValue = new DateValue("ABSOLUTE", value, value); + this.setDateValue(newValue); this.isOpen = false; } diff --git a/pdb-js/src/app/plot-view/plot-view.component.ts b/pdb-js/src/app/plot-view/plot-view.component.ts index 2029fe3..1a74614 100644 --- a/pdb-js/src/app/plot-view/plot-view.component.ts +++ b/pdb-js/src/app/plot-view/plot-view.component.ts @@ -6,6 +6,7 @@ import { WidgetDimensions } from '../dashboard.service'; import { Overlay } from "@angular/cdk/overlay"; import { DateTime, Duration } from "luxon"; +import { DateValue } from '../components/datepicker/date-picker.component'; @Component({ selector: 'pdb-plot-view', @@ -32,7 +33,7 @@ export class PlotViewComponent { loadingEvent : EventEmitter = new EventEmitter(); @Output() - dateRangeUpdateEvent : EventEmitter = new EventEmitter(); + dateRangeUpdateEvent : EventEmitter = new EventEmitter(); in_drag_mode = false; drag_start_x = 0; @@ -184,16 +185,16 @@ export class PlotViewComponent { const formattedEndDate = endDate.toFormat(this.DATE_PATTERN); const newDateRange = formattedStartDate+" - "+formattedEndDate; - - this.dateRangeUpdateEvent.emit(newDateRange); + const newDateValue = new DateValue('ABSOLUTE', newDateRange, newDateRange); + this.dateRangeUpdateEvent.emit(newDateValue); } 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){ - this.shiftDateByAnchor(this.config?.dateRange!, dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor); + this.shiftDateByAnchor(this.config?.dateRange.value!, dateAnchor.cursorPercentOfDateRange, dateAnchor.zoomFactor); } zoomByScroll(event: WheelEvent) { diff --git a/pdb-js/src/app/plot.service.ts b/pdb-js/src/app/plot.service.ts index b4c4a20..f430967 100644 --- a/pdb-js/src/app/plot.service.ts +++ b/pdb-js/src/app/plot.service.ts @@ -1,190 +1,352 @@ -import { Injectable, OnInit } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - +import { Injectable, OnInit } from "@angular/core"; +import { HttpClient, HttpParams } from "@angular/common/http"; +import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; +import { DateValue } from "./components/datepicker/date-picker.component"; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class PlotService { - plotTypes: Array; - + constructor(private http: HttpClient) { this.plotTypes = new Array(); - this.plotTypes.push(new PlotType("SCATTER","Scatter","scatter-chart2",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(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 { - return this.plotTypes.filter(plotType => plotType.active); - } - - getTagFields(): Observable> { - return this.http.get>('//'+window.location.hostname+':'+window.location.port+'/api/fields'); - } - - autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable - { - const options = { - params: new HttpParams() - .set('caretIndex', ""+caretIndex) - .set('query', query) - .set('resultMode', resultMode) - }; - return this.http.get('//'+window.location.hostname+':'+window.location.port+'/api/autocomplete', options); + this.plotTypes.push( + new PlotType( + "SCATTER", + "Scatter", + "scatter-chart2", + 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( + 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, + ), + ); } - abort(submitterId: string): Observable{ - return this.http.delete('//'+window.location.hostname+':'+window.location.port+'/api/plots/'+submitterId) + getPlotTypes(): Array { + return this.plotTypes.filter((plotType) => plotType.active); } - - sendPlotRequest(plotRequest: PlotRequest): Observable{ - + + getTagFields(): Observable> { + return this.http.get>( + "//" + window.location.hostname + ":" + window.location.port + + "/api/fields", + ); + } + + autocomplete( + query: string, + caretIndex: number, + resultMode: ResultMode, + ): Observable { + const options = { + params: new HttpParams() + .set("caretIndex", "" + caretIndex) + .set("query", query) + .set("resultMode", resultMode), + }; + return this.http.get( + "//" + window.location.hostname + ":" + window.location.port + + "/api/autocomplete", + options, + ); + } + + abort(submitterId: string): Observable { + return this.http.delete( + "//" + window.location.hostname + ":" + window.location.port + + "/api/plots/" + submitterId, + ); + } + + sendPlotRequest(plotRequest: PlotRequest): Observable { //console.log("send plot request: "+ JSON.stringify(plotRequest)); - const result = this.http.post('//'+window.location.hostname+':'+window.location.port+'/api/plots', plotRequest); + const result = this.http.post( + "//" + window.location.hostname + ":" + window.location.port + + "/api/plots", + plotRequest, + ); return result.pipe(map(this.enrichStats)); } enrichStats(response: PlotResponse): PlotResponse { - let maxAvgRatio = 0; - let x : DataSeriesStats[] = response.stats.dataSeriesStats; - for (const row in x){ - for (const col in x){ + let maxAvgRatio = 0; + let x: DataSeriesStats[] = response.stats.dataSeriesStats; + for (const row in x) { + for (const col in x) { maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average); } } - + response.stats.maxAvgRatio = maxAvgRatio; return response; } - - getFilterDefaults(): Observable{ - return this.http.get('//'+window.location.hostname+':'+window.location.port+'/api/filters/defaults') + + getFilterDefaults(): Observable { + return this.http.get( + "//" + window.location.hostname + ":" + window.location.port + + "/api/filters/defaults", + ); } - - splitQuery(query: string, splitBy:string) : Observable>{ - - const q = "("+query+") and "+splitBy+"="; - return this.autocomplete(q, q.length+1, ResultMode.FULL_VALUES).pipe( + + splitQuery(query: string, splitBy: string): Observable> { + const q = "(" + query + ") and " + splitBy + "="; + return this.autocomplete(q, q.length + 1, ResultMode.FULL_VALUES).pipe( map( - (autocompleteResult: AutocompleteResult) => autocompleteResult.proposals.map((suggestion:Suggestion) => suggestion.value) - ) + (autocompleteResult: AutocompleteResult) => + autocompleteResult.proposals.map((suggestion: Suggestion) => + suggestion.value + ), + ), ); } } - export class PlotType { - constructor( - public id: string, - public name: string, - public icon: string, - public active: boolean, - public xAxis: DataType, - public yAxis: DataType) { - + public id: string, + public name: string, + public icon: string, + public active: boolean, + public xAxis: DataType, + public yAxis: DataType, + ) { } - - compatible(others: Array) : boolean { + + compatible(others: Array): boolean { var xAxisTypes = new Set([this.xAxis]); 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]; xAxisTypes.add(other.xAxis); yAxisTypes.add(other.yAxis); } - + return xAxisTypes.size <= 2 && yAxisTypes.size <= 2; } } export class TagField { name: string; - + constructor(name: string) { this.name = name; } } export enum DataType { - Time, - Duration, - Percent, - Count, - Group, - Metric, - HistogramBin, - HistogramCount, - Other + Time, + Duration, + Percent, + Count, + Group, + Metric, + HistogramBin, + HistogramCount, + Other, } export class AxesTypes { - x : Array; - y : Array; - - constructor(x: Array, y : Array) { + x: Array; + y: Array; + + constructor(x: Array, y: Array) { this.x = x; this.y = y; } - - hasXAxis(type : DataType){ + + hasXAxis(type: DataType) { return this.x.includes(type); } - - hasYAxis(type : DataType){ + + hasYAxis(type: DataType) { 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){ - if (this.x.length+1 >= index){ - return this.x[index-1]; + getXAxisDataType(index: number) { + if (this.x.length + 1 >= index) { + return this.x[index - 1]; } return undefined; } - + /** - * 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){ - if (this.y.length+1 >= index){ - return this.y[index-1]; + getYAxisDataType(index: number) { + if (this.y.length + 1 >= index) { + return this.y[index - 1]; } return undefined; } - + toString() { const x1 = this.getXAxisDataType(1); const y1 = this.getYAxisDataType(1); const x2 = this.getXAxisDataType(2); const y2 = this.getYAxisDataType(2); - - return (x1 ? "x1:"+DataType[x1] : "") - + (y1 ? " y1:"+DataType[y1] : "") - + (x2 ? " x2:"+DataType[x2] : "") - + (y2 ? " y2:"+DataType[y2] : ""); + + return (x1 ? "x1:" + DataType[x1] : "") + + (y1 ? " y1:" + DataType[y1] : "") + + (x2 ? " x2:" + DataType[x2] : "") + + (y2 ? " y2:" + DataType[y2] : ""); } } @@ -192,12 +354,12 @@ export class Suggestion { constructor( public value: string, public newQuery: string, - public newCaretPosition: number){} + public newCaretPosition: number, + ) {} } - -export class AutocompleteResult{ - constructor(public proposals: Array){} +export class AutocompleteResult { + constructor(public proposals: Array) {} } export type RenderOptionsMap = { @@ -212,86 +374,94 @@ export class PlotRequest { constructor( public submitterId: string, public config: PlotConfig, - public renders: RenderOptionsMap - ){} - - + public renders: RenderOptionsMap, + ) {} + copy(): PlotRequest { return JSON.parse(JSON.stringify(this)); } } export class PlotConfig { - constructor( public query : string, - public groupBy : Array, - public limitBy : string, - public limit : number, - public y1:YAxisDefinition, - public y2:YAxisDefinition|undefined, - public dateRange : string, - public aggregates : Array, + constructor( + public query: string, + public groupBy: Array, + public limitBy: string, + public limit: number, + public y1: YAxisDefinition, + public y2: YAxisDefinition | undefined, + public dateRange: DateValue, + public aggregates: Array, public intervalUnit: string, public intervalValue: number, - public renderBarChartTickLabels: boolean = false,) {} + public renderBarChartTickLabels: boolean = false, + ) {} } export class RenderOptions { constructor( - public height: number, - public width: number, - public showKey: boolean, - public renderLabels: boolean) {} + public height: number, + public width: number, + public showKey: boolean, + public renderLabels: boolean, + ) {} } export class YAxisDefinition { constructor( - public axisScale : string, - public rangeMin : number, - public rangeMax : number, - public rangeUnit : string){} + public axisScale: string, + public rangeMin: number, + public rangeMax: number, + public rangeUnit: string, + ) {} } export class PlotResponse { constructor( - public stats : PlotResponseStats, - public rendered: RenderedImages){} + public stats: PlotResponseStats, + public rendered: RenderedImages, + ) {} } export class PlotResponseStats { constructor( - public maxValue : number, - public values : number, - public average : number, - public plottedValues : number, + public maxValue: number, + public values: number, + public average: number, + public plottedValues: number, public maxAvgRatio: number, - public dataSeriesStats : Array){} + public dataSeriesStats: Array, + ) {} } export class DataSeriesStats { constructor( public name: string, - public values : number, - public maxValue : number, - public average : number , - public plottedValues : number, + public values: number, + public maxValue: number, + public average: number, + public plottedValues: number, public dashTypeAndColor: DashTypeAndColor, - public percentiles: Map){} + public percentiles: Map, + ) {} } export class DashTypeAndColor { constructor( public color: string, - public pointType: number) {} + public pointType: number, + ) {} } export class FilterDefaults { constructor( public groupBy: Array, - public fields: Array, - public splitBy: string){} + public fields: Array, + public splitBy: string, + ) {} } export enum ResultMode { - CUT_AT_DOT = "CUT_AT_DOT", - FULL_VALUES = "FULL_VALUES" + CUT_AT_DOT = "CUT_AT_DOT", + FULL_VALUES = "FULL_VALUES", } diff --git a/pdb-js/src/app/visualization-page/visualization-page.component.html b/pdb-js/src/app/visualization-page/visualization-page.component.html index 414731e..05b7498 100644 --- a/pdb-js/src/app/visualization-page/visualization-page.component.html +++ b/pdb-js/src/app/visualization-page/visualization-page.component.html @@ -1,113 +1,155 @@
- +
- +
+ +
- +
Type: - - - {{plotType.name}} + + + + {{ plotType.name }} - + Group By: - {{tagField.name}} + {{ + tagField.name + }} - +
- - Intervals (only bar chart): - - - - second - minute - hour - day - week - month - year - - + + Intervals (only bar chart): + + - + second + minute + hour + day + week + month + year + +
- Show Tic Labels (bar chart) + Show Tic Labels (bar chart)
- - - - Gallery - - + + + + Gallery + + Split By: - {{tagField.name}} + {{ + tagField.name + }} Please select a value! - - +
- - - - + matTooltip="abort" + > + Abort +
- +
- - + (dateRangeUpdateEvent)="updateDateRange($event)" + > +
-
- - diff --git a/pdb-js/src/app/visualization-page/visualization-page.component.ts b/pdb-js/src/app/visualization-page/visualization-page.component.ts index 63141e3..9402cff 100644 --- a/pdb-js/src/app/visualization-page/visualization-page.component.ts +++ b/pdb-js/src/app/visualization-page/visualization-page.component.ts @@ -1,21 +1,46 @@ -import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core'; -import { PlotService, PlotType, PlotRequest, TagField, FilterDefaults, DataType, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap, Suggestion } 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 { PlotViewComponent, LoadingEvent } from '../plot-view/plot-view.component'; -import { GalleryViewComponent } from '../gallery-view/gallery-view.component'; -import { WidgetDimensions } from '../dashboard.service'; +import { + AfterViewInit, + Component, + Input, + OnInit, + ViewChild, +} from "@angular/core"; +import { + AxesTypes, + 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({ - selector: 'pdb-visualization-page', - templateUrl: './visualization-page.component.html', - styleUrls: ['./visualization-page.component.scss'] + selector: "pdb-visualization-page", + templateUrl: "./visualization-page.component.html", + styleUrls: ["./visualization-page.component.scss"], }) export class VisualizationPageComponent implements OnInit, AfterViewInit { - readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS @Input() @@ -23,47 +48,50 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit { @Input() 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(); plotTypes: PlotType[] = []; - + tagFields: Array = new Array(); - + groupBy = new Array(); - - @ViewChild('limitbycomponent') - private limitbycomponent! : LimitByComponent; - - - @ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent }) - private y1AxisDefinitionComponent! : YAxisDefinitionComponent; - - @ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent }) - private y2AxisDefinitionComponent! : YAxisDefinitionComponent; - - @ViewChild('query') + + @ViewChild("limitbycomponent") + private limitbycomponent!: LimitByComponent; + + @ViewChild("y1AxisDefinitionComponent", { read: YAxisDefinitionComponent }) + private y1AxisDefinitionComponent!: YAxisDefinitionComponent; + + @ViewChild("y2AxisDefinitionComponent", { read: YAxisDefinitionComponent }) + private y2AxisDefinitionComponent!: YAxisDefinitionComponent; + + @ViewChild("query") query!: QueryAutocompleteComponent; - - @ViewChild('plotView') + + @ViewChild("plotView") plotView!: PlotViewComponent; - - @ViewChild('galleryView') + + @ViewChild("galleryView") galleryView!: GalleryViewComponent; - + + @ViewChild("datePicker") + datePicker!: DatePickerComponent; + enableGallery = false; - splitBy : TagField | undefined = undefined; + splitBy: TagField | undefined = undefined; y2AxisAvailable = false; - - intervalUnit = 'NO_INTERVAL'; + + intervalUnit = "NO_INTERVAL"; intervalValue = 1; renderBarChartTickLabels = false; plotJobActive = false; constructor(private plotService: PlotService, private snackBar: MatSnackBar) { - const params = new URLSearchParams(window.location.search); if (!this.defaultConfig && params.get("config")) { const config = JSON.parse(params.get("config")!); @@ -71,118 +99,128 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit { } } - showError(message:string) { + showError(message: string) { this.snackBar.open(message, "", { duration: 5000, - verticalPosition: 'top' + verticalPosition: "top", }); } ngOnInit() { - - (window).initDatePicker(); + ( window).initDatePicker(); this.plotTypes = this.plotService.getPlotTypes(); this.selectedPlotType.push(this.plotTypes[0]); - - this.plotService.getFilterDefaults().subscribe((filterDefaults: FilterDefaults) => { - - filterDefaults.fields.forEach((name:string) => { - this.tagFields.push(new TagField(name)); - }, - (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(); - } - }); + this.plotService.getFilterDefaults().subscribe( + (filterDefaults: FilterDefaults) => { + filterDefaults.fields.forEach((name: string) => { + this.tagFields.push(new TagField(name)); + }, (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 { - if (this.defaultConfig) { - const c = this.defaultConfig; - this.query.suggestionFetcherEnabled = false; - this.query.queryField.setValue(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.changePlotType(this.selectedPlotType); - this.updateDateRange(c.dateRange, false); - this.limitbycomponent.limitBy = c.limitBy; - this.limitbycomponent.limit = c.limit; - this.intervalUnit = c.intervalUnit; - this.intervalValue = c.intervalValue; - this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale; - this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin; - this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax; - this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit; + ngAfterViewInit(): void { + if (this.defaultConfig) { + const c = this.defaultConfig; + this.query.suggestionFetcherEnabled = false; + this.query.queryField.setValue( + new Suggestion(c.query, c.query, c.query.length), + ); + this.query.suggestionFetcherEnabled = true; - if (c.y2) { - this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale; - this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin; - this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax; - this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit; + this.selectedPlotType = this.plotTypes.filter((pt) => + c.aggregates.includes(pt.id) + ); + this.changePlotType(this.selectedPlotType); + this.updateDateRange(c.dateRange, false); + this.limitbycomponent.limitBy = c.limitBy; + this.limitbycomponent.limit = c.limit; + this.intervalUnit = c.intervalUnit; + this.intervalValue = c.intervalValue; + this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale; + this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin; + this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax; + this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit; + + if (c.y2) { + this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale; + this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin; + this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax; + this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit; + } } } -} -toggleGallery(event: Event){ - this.galleryView.show = this.enableGallery; -} + toggleGallery(event: Event) { + this.galleryView.show = this.enableGallery; + } loading(event: LoadingEvent) { this.plotJobActive = event.loading; } - updateDateRange(newDateRange: string, updatePlot=true) { - (document.getElementById("search-date-range")).value = newDateRange; - if (updatePlot){ + updateDateRange(newDateRange: DateValue, updatePlot = true) { + this.datePicker.setDateValue(newDateRange); + if (updatePlot) { this.plot(); } } changePlotType(selectedPlotTypes: Array) { - const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes)); - this.plotTypes.forEach(pt => pt.active=false); - compatiblePlotTypes.forEach(pt => pt.active=true); - + const compatiblePlotTypes = this.plotTypes.filter((pt) => + pt.compatible(selectedPlotTypes) + ); + this.plotTypes.forEach((pt) => pt.active = false); + compatiblePlotTypes.forEach((pt) => pt.active = true); + const axesTypes = this.getAxes(); this.y2AxisAvailable = axesTypes.y.length == 2; } - - selectedPlotTypesContains(plotTypeIds: Array){ - return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0; + + selectedPlotTypesContains(plotTypeIds: Array) { + return this.selectedPlotType.filter((pt) => plotTypeIds.includes(pt.id)) + .length > 0; } - - - dateRangeAsString() : string { - return (document.getElementById("search-date-range")).value; + + dateRangeAsString(): DateValue { + return this.datePicker.getDateValue(); } - - gallery(){ - if (this.splitBy != null){ - this.plotView.imageUrl = ''; + + gallery() { + if (this.splitBy != null) { + this.plotView.imageUrl = ""; this.plotView.stats = null; - this.galleryView.show=true; + this.galleryView.show = true; const request = this.createPlotRequest(); this.galleryView.renderGallery(request, this.splitBy.name); } else { console.error("variable splitBy was null when rendering gallery"); } } - - getAxes() : AxesTypes { - + + getAxes(): AxesTypes { const x = new Array(); const y = new Array(); - - for(var i = 0; i < this.selectedPlotType.length; i++){ + + for (var i = 0; i < this.selectedPlotType.length; i++) { var plotType = this.selectedPlotType[i]; if (!x.includes(plotType.xAxis)) { x.push(plotType.xAxis); @@ -191,75 +229,83 @@ toggleGallery(event: Event){ y.push(plotType.yAxis); } } - - return new AxesTypes(x,y); + + return new AxesTypes(x, y); } abort() { - this.plotService.abort((window).submitterId).subscribe({ + this.plotService.abort(( window).submitterId).subscribe({ complete: () => { - } + }, }); } - - plot(){ + + plot() { const config = this.createPlotConfig(); this.plotView.plot(config, this.plotDimensionSupplier); } - plotDimensionSupplier(): WidgetDimensions{ + plotDimensionSupplier(): WidgetDimensions { const results = document.getElementById("results"); return new WidgetDimensions( - results != null ? results.offsetWidth-1 : 1024, - results != null ? results.offsetHeight-1: 1024); + results != null ? results.offsetWidth - 1 : 1024, + results != null ? results.offsetHeight - 1 : 1024, + ); } createPlotConfig(): PlotConfig { const aggregates = new Array(); - this.selectedPlotType.forEach(a => aggregates.push(a.id)); - + this.selectedPlotType.forEach((a) => aggregates.push(a.id)); + 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( this.query.query, - this.groupBy.map(o => o.name), + this.groupBy.map((o) => o.name), this.limitbycomponent.limitBy, this.limitbycomponent.limit, y1, y2, - this.dateRangeAsString(), // dateRange - aggregates, // aggregates + this.datePicker.getDateValue(), // dateRange + aggregates, // aggregates this.intervalUnit, this.intervalValue, - this.renderBarChartTickLabels, + this.renderBarChartTickLabels, ); return config; } - + createPlotRequest(): PlotRequest { const results = document.getElementById("results"); - + const config = this.createPlotConfig(); - const renderOptions : RenderOptionsMap = { - 'main': new RenderOptions(results!.offsetHeight-1, results!.offsetWidth-1, true, true), - 'thumbnail': new RenderOptions(200, 300, false, false), + const renderOptions: RenderOptionsMap = { + "main": new RenderOptions( + results!.offsetHeight - 1, + results!.offsetWidth - 1, + true, + true, + ), + "thumbnail": new RenderOptions(200, 300, false, false), }; - + const request = new PlotRequest( - (window).submitterId, + ( window).submitterId, config, - renderOptions - ); + renderOptions, + ); return request; } serializedConfig(): string { - try{ - const config = this.createPlotConfig(); - return JSON.stringify(config); - }catch (e) { + try { + const config = this.createPlotConfig(); + return JSON.stringify(config); + } catch (e) { return ""; } } diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java index 79ebd2c..11ace4a 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateTimeRangeParser.java @@ -25,78 +25,98 @@ public class DateTimeRangeParser { private static OffsetDateTime parseInternal(final OffsetDateTime offsetTime, final String timeDefinition) { - final Pattern regex = Pattern.compile("(?[BE]?)(?\\-?[0-9]*)(?[mHDWMY])"); + final Pattern regex = Pattern.compile("(?[BE])(?(\\-?[0-9]*[mHDWMY])+)", + Pattern.MULTILINE); final Matcher matcher = regex.matcher(timeDefinition); + if (matcher.matches()) { + final String beginEnd = matcher.group("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) { - case "m": { - final ChronoUnit unit = ChronoUnit.MINUTES; - if (begin) { - return offsetTime.plus(amount, unit).truncatedTo(unit); - } else { - return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1); + OffsetDateTime result = offsetTime; + + final String amountUnitString = matcher.group("amountUnit"); + final Pattern regexAmountUnit = Pattern.compile("(?\\-?[0-9]*)(?[mHDWMY])"); + final Matcher m = regexAmountUnit.matcher(amountUnitString); + while (m.find()) { + 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": { - 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); - } + return result; } throw new IllegalArgumentException("invalid input: " + timeDefinition); diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateValue.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateValue.java new file mode 100644 index 0000000..33adfe0 --- /dev/null +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/DateValue.java @@ -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; + } + +} diff --git a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java index 51da48b..92e78d8 100644 --- a/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java +++ b/pdb-plotting/src/main/java/org/lucares/pdb/plot/api/PlotSettings.java @@ -17,7 +17,7 @@ import org.lucares.utils.Preconditions; 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; @@ -27,7 +27,7 @@ public class PlotSettings { private int limit; - private String dateRangeAsString; + private DateValue dateValue; private YAxisDefinition y1; private YAxisDefinition y2; @@ -80,30 +80,37 @@ public class PlotSettings { this.limit = limit; } - public String getDateRange() { - return dateRangeAsString; + public DateValue getDateRange() { + return dateValue; } - public void setDateRange(final String dateRangeAsString) { - this.dateRangeAsString = dateRangeAsString; + public void setDateRange(final DateValue dateValue) { + this.dateValue = dateValue; } public DateTimeRange dateRange() { - final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - ")); - Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString); + switch (this.dateValue.getType()) { + 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 endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC); - - return new DateTimeRange(startDate, endDate); + final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC); + final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC); + return new DateTimeRange(startDate, endDate); + } + throw new UnsupportedOperationException(); } @Override public String toString() { 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 + "]"; } diff --git a/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java b/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java index d934c74..b714d1c 100644 --- a/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java +++ b/pdb-plotting/src/test/java/org/lucares/pdb/plot/api/DateTimeRangeParserTest.java @@ -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"), // 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", - // "2024-03-29 12:00: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") ); } @@ -104,4 +103,31 @@ public class DateTimeRangeParserTest { Assertions.assertEquals(expectedStart, actualStart, "start"); Assertions.assertEquals(expectedEnd, actualEnd, "end"); } + + public static Stream 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"); + } } diff --git a/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotConfig.java b/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotConfig.java index 70059f2..c33fbb6 100644 --- a/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotConfig.java +++ b/pdb-ui/src/main/java/org/lucares/pdbui/domain/PlotConfig.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; 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.YAxisDefinition; @@ -25,7 +26,7 @@ public class PlotConfig { private YAxisDefinition y1 = new YAxisDefinition(); private YAxisDefinition y2 = new YAxisDefinition(); - private String dateRange; + private DateValue dateRange; private List aggregates = new ArrayList<>(); @@ -66,11 +67,11 @@ public class PlotConfig { this.limit = limit; } - public String getDateRange() { + public DateValue getDateRange() { return dateRange; } - public void setDateRange(final String dateRange) { + public void setDateRange(final DateValue dateRange) { this.dateRange = dateRange; }