dashboard #1

Merged
andi merged 118 commits from dashboard into master 2024-09-29 06:47:35 +00:00
13 changed files with 856 additions and 478 deletions
Showing only changes of commit 21a84b5223 - Show all commits

View File

@@ -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>

View File

@@ -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;
}
} }

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -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) {

View File

@@ -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",
} }

View File

@@ -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>

View File

@@ -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 "";
} }
} }

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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 + "]";
} }

View File

@@ -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");
}
} }

View File

@@ -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;
} }