dashboard #1
@@ -1,5 +1,15 @@
|
||||
<style>
|
||||
<style></style>
|
||||
|
||||
</style>
|
||||
<form>
|
||||
<app-date-picker
|
||||
[formControl]="datePicker"
|
||||
(dateValueSelected)="(dateChanged)"
|
||||
></app-date-picker>
|
||||
|
||||
<app-date-picker></app-date-picker>
|
||||
Type: {{ datePicker.value?.type }}<br />
|
||||
value: {{ datePicker.value?.value }}<br />
|
||||
display: {{ datePicker.value?.display }}<br />
|
||||
</form>
|
||||
|
||||
<button (click)="readValue()">read value</button>
|
||||
<div>{{ output }}</div>
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import { Component} from '@angular/core';
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { DateValue } from "./date-picker.component";
|
||||
|
||||
@Component({
|
||||
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 {
|
||||
datePicker = new FormControl<DateValue>(
|
||||
new DateValue(
|
||||
"ABSOLUTE",
|
||||
"2019-10-05 12:34:56 - 2019-10-11 23:59:59",
|
||||
"2019-10-05 12:34:56 - 2019-10-11 23:59:59",
|
||||
),
|
||||
);
|
||||
|
||||
constructor(){
|
||||
output = "";
|
||||
|
||||
readValue() {
|
||||
this.output = this.datePicker.value?.type + " " +
|
||||
this.datePicker.value?.value;
|
||||
}
|
||||
|
||||
dateChanged(e: any) {
|
||||
console.log("dateChanged", e);
|
||||
this.output += "dateChanged: " + e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
<mat-tab label="Quick">
|
||||
<div class="tab-quick">
|
||||
<div class="tab-quick-column">
|
||||
<button mat-button (click)="applyQuick('BD/P1D', 'today')">
|
||||
<button mat-button (click)="applyQuick('BD/E1D', 'today')">
|
||||
Today
|
||||
</button>
|
||||
<button mat-button (click)="applyQuick('B-1D/P1D', 'yesterday')">
|
||||
<button mat-button (click)="applyQuick('B-1D/E-1D', 'yesterday')">
|
||||
Yesterday
|
||||
</button>
|
||||
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
|
||||
@@ -70,9 +70,6 @@
|
||||
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
|
||||
This Month
|
||||
</button>
|
||||
<button mat-button (click)="applyQuick('BQ/EQ', 'this quarter')">
|
||||
This Quarter
|
||||
</button>
|
||||
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
|
||||
This Year
|
||||
</button>
|
||||
|
||||
@@ -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 {
|
||||
//(<any> 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 = <string> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
||||
|
||||
@Output()
|
||||
dateRangeUpdateEvent : EventEmitter<string> = new EventEmitter<string>();
|
||||
dateRangeUpdateEvent : EventEmitter<DateValue> = new EventEmitter<DateValue>();
|
||||
|
||||
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) {
|
||||
|
||||
@@ -1,72 +1,231 @@
|
||||
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<PlotType>;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.plotTypes = new Array<PlotType>();
|
||||
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(
|
||||
"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));
|
||||
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<PlotType> {
|
||||
return this.plotTypes.filter(plotType => plotType.active);
|
||||
return this.plotTypes.filter((plotType) => plotType.active);
|
||||
}
|
||||
|
||||
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 = {
|
||||
params: new HttpParams()
|
||||
.set('caretIndex', ""+caretIndex)
|
||||
.set('query', query)
|
||||
.set('resultMode', resultMode)
|
||||
.set("caretIndex", "" + caretIndex)
|
||||
.set("query", query)
|
||||
.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>{
|
||||
return this.http.delete<void>('//'+window.location.hostname+':'+window.location.port+'/api/plots/'+submitterId)
|
||||
abort(submitterId: string): Observable<void> {
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
enrichStats(response: PlotResponse): PlotResponse {
|
||||
let maxAvgRatio = 0;
|
||||
let x : DataSeriesStats[] = response.stats.dataSeriesStats;
|
||||
for (const row in x){
|
||||
for (const col in x){
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -76,39 +235,42 @@ export class PlotService {
|
||||
return response;
|
||||
}
|
||||
|
||||
getFilterDefaults(): Observable<FilterDefaults>{
|
||||
return this.http.get<FilterDefaults>('//'+window.location.hostname+':'+window.location.port+'/api/filters/defaults')
|
||||
getFilterDefaults(): Observable<FilterDefaults> {
|
||||
return this.http.get<FilterDefaults>(
|
||||
"//" + window.location.hostname + ":" + window.location.port +
|
||||
"/api/filters/defaults",
|
||||
);
|
||||
}
|
||||
|
||||
splitQuery(query: string, splitBy:string) : Observable<Array<string>>{
|
||||
|
||||
const q = "("+query+") and "+splitBy+"=";
|
||||
return this.autocomplete(q, q.length+1, ResultMode.FULL_VALUES).pipe(
|
||||
splitQuery(query: string, splitBy: string): Observable<Array<string>> {
|
||||
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 yAxis: DataType,
|
||||
) {
|
||||
}
|
||||
|
||||
compatible(others: Array<PlotType>) : boolean {
|
||||
compatible(others: Array<PlotType>): 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);
|
||||
@@ -135,32 +297,32 @@ export enum DataType {
|
||||
Metric,
|
||||
HistogramBin,
|
||||
HistogramCount,
|
||||
Other
|
||||
Other,
|
||||
}
|
||||
|
||||
export class AxesTypes {
|
||||
x : Array<DataType>;
|
||||
y : Array<DataType>;
|
||||
x: Array<DataType>;
|
||||
y: Array<DataType>;
|
||||
|
||||
constructor(x: Array<DataType>, y : Array<DataType>) {
|
||||
constructor(x: Array<DataType>, y: Array<DataType>) {
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -168,9 +330,9 @@ export class AxesTypes {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -181,10 +343,10 @@ export class AxesTypes {
|
||||
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<Suggestion>){}
|
||||
export class AutocompleteResult {
|
||||
constructor(public proposals: Array<Suggestion>) {}
|
||||
}
|
||||
|
||||
export type RenderOptionsMap = {
|
||||
@@ -212,9 +374,8 @@ export class PlotRequest {
|
||||
constructor(
|
||||
public submitterId: string,
|
||||
public config: PlotConfig,
|
||||
public renders: RenderOptionsMap
|
||||
){}
|
||||
|
||||
public renders: RenderOptionsMap,
|
||||
) {}
|
||||
|
||||
copy(): PlotRequest {
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
@@ -222,17 +383,19 @@ export class PlotRequest {
|
||||
}
|
||||
|
||||
export class PlotConfig {
|
||||
constructor( public query : string,
|
||||
public groupBy : Array<string>,
|
||||
public limitBy : string,
|
||||
public limit : number,
|
||||
public y1:YAxisDefinition,
|
||||
public y2:YAxisDefinition|undefined,
|
||||
public dateRange : string,
|
||||
public aggregates : Array<string>,
|
||||
constructor(
|
||||
public query: string,
|
||||
public groupBy: Array<string>,
|
||||
public limitBy: string,
|
||||
public limit: number,
|
||||
public y1: YAxisDefinition,
|
||||
public y2: YAxisDefinition | undefined,
|
||||
public dateRange: DateValue,
|
||||
public aggregates: Array<string>,
|
||||
public intervalUnit: string,
|
||||
public intervalValue: number,
|
||||
public renderBarChartTickLabels: boolean = false,) {}
|
||||
public renderBarChartTickLabels: boolean = false,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class RenderOptions {
|
||||
@@ -240,58 +403,65 @@ export class RenderOptions {
|
||||
public height: number,
|
||||
public width: number,
|
||||
public showKey: boolean,
|
||||
public renderLabels: 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<DataSeriesStats>){}
|
||||
public dataSeriesStats: Array<DataSeriesStats>,
|
||||
) {}
|
||||
}
|
||||
|
||||
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<string, number>){}
|
||||
public percentiles: Map<string, number>,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class DashTypeAndColor {
|
||||
constructor(
|
||||
public color: string,
|
||||
public pointType: number) {}
|
||||
public pointType: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class FilterDefaults {
|
||||
constructor(
|
||||
public groupBy: Array<string>,
|
||||
public fields: Array<string>,
|
||||
public splitBy: string){}
|
||||
public splitBy: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
export enum ResultMode {
|
||||
CUT_AT_DOT = "CUT_AT_DOT",
|
||||
FULL_VALUES = "FULL_VALUES"
|
||||
FULL_VALUES = "FULL_VALUES",
|
||||
}
|
||||
|
||||
@@ -4,19 +4,31 @@
|
||||
</div>
|
||||
|
||||
<div id="date-box">
|
||||
<app-date-picker #datePicker></app-date-picker>
|
||||
<!--
|
||||
<mat-form-field class="pdb-form-full-width">
|
||||
<mat-label>Date Range:</mat-label>
|
||||
<input matInput id="search-date-range" value="dateRange" name="dates" />
|
||||
</mat-form-field>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div id="filters">
|
||||
<div id="filterpanel">
|
||||
<mat-form-field class="pdb-form-full-width">
|
||||
<mat-label>Type:</mat-label>
|
||||
<mat-select multiple [(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-select
|
||||
multiple
|
||||
[(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-select>
|
||||
</mat-form-field>
|
||||
@@ -24,13 +36,15 @@
|
||||
<mat-form-field class="pdb-form-full-width">
|
||||
<mat-label>Group By:</mat-label>
|
||||
<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-form-field>
|
||||
|
||||
<pdb-limit-by #limitbycomponent></pdb-limit-by>
|
||||
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
||||
<mat-form-field >
|
||||
<mat-form-field>
|
||||
<mat-label>Intervals (only bar chart):</mat-label>
|
||||
<mat-select [(value)]="intervalUnit">
|
||||
<mat-option value="NO_INTERVAL">-</mat-option>
|
||||
@@ -45,39 +59,62 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<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>
|
||||
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
|
||||
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
|
||||
<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-label>Split By:</mat-label>
|
||||
<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-error *ngIf="splitBy == null || true">
|
||||
Please select a value!
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
<div id="plot-button-bar">
|
||||
<a
|
||||
mat-icon-button
|
||||
[routerLink]="['/vis']"
|
||||
[queryParams]="{config: serializedConfig()}"
|
||||
[queryParams]="{ config: serializedConfig() }"
|
||||
target="_blank"
|
||||
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
|
||||
*ngIf="!enableGallery && !plotJobActive"
|
||||
[disabled]="plotJobActive"
|
||||
mat-button
|
||||
matTooltip="Create Plot"
|
||||
(click)="plot()">
|
||||
<img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" />
|
||||
(click)="plot()"
|
||||
>
|
||||
<img
|
||||
src="assets/img/scatter-chart2.svg"
|
||||
class="icon-inline"
|
||||
aria-hidden="true"
|
||||
title="create plot"
|
||||
/>
|
||||
Plot
|
||||
</button>
|
||||
<button
|
||||
@@ -85,15 +122,24 @@
|
||||
mat-button
|
||||
matTooltip="Create Gallery"
|
||||
(click)="gallery()"
|
||||
[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)" />
|
||||
[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)"
|
||||
/>
|
||||
Gallery
|
||||
</button>
|
||||
<button
|
||||
*ngIf="plotJobActive"
|
||||
mat-button
|
||||
(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>
|
||||
@@ -102,12 +148,8 @@
|
||||
<pdb-plot-view
|
||||
#plotView
|
||||
(loadingEvent)="loading($event)"
|
||||
(dateRangeUpdateEvent)="updateDateRange($event)"></pdb-plot-view>
|
||||
<pdb-gallery-view
|
||||
#galleryView>
|
||||
</pdb-gallery-view>
|
||||
(dateRangeUpdateEvent)="updateDateRange($event)"
|
||||
></pdb-plot-view>
|
||||
<pdb-gallery-view #galleryView> </pdb-gallery-view>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -24,7 +49,9 @@ 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<PlotType>();
|
||||
plotTypes: PlotType[] = [];
|
||||
@@ -33,37 +60,38 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||
|
||||
groupBy = new Array<TagField>();
|
||||
|
||||
@ViewChild('limitbycomponent')
|
||||
private limitbycomponent! : LimitByComponent;
|
||||
@ViewChild("limitbycomponent")
|
||||
private limitbycomponent!: LimitByComponent;
|
||||
|
||||
@ViewChild("y1AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||
private y1AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||
|
||||
@ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
||||
private y1AxisDefinitionComponent! : YAxisDefinitionComponent;
|
||||
@ViewChild("y2AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||
private y2AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||
|
||||
@ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
||||
private y2AxisDefinitionComponent! : YAxisDefinitionComponent;
|
||||
|
||||
@ViewChild('query')
|
||||
@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,47 +99,56 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
showError(message:string) {
|
||||
showError(message: string) {
|
||||
this.snackBar.open(message, "", {
|
||||
duration: 5000,
|
||||
verticalPosition: 'top'
|
||||
verticalPosition: "top",
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
(<any>window).initDatePicker();
|
||||
(<any> 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.plotService.getFilterDefaults().subscribe(
|
||||
(filterDefaults: FilterDefaults) => {
|
||||
filterDefaults.fields.forEach((name: string) => {
|
||||
this.tagFields.push(new TagField(name));
|
||||
},
|
||||
(error: any) => {
|
||||
}, (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);
|
||||
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) {
|
||||
const c = this.defaultConfig;
|
||||
this.query.suggestionFetcherEnabled = false;
|
||||
this.query.queryField.setValue(new Suggestion(c.query, c.query, c.query.length));
|
||||
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.selectedPlotType = this.plotTypes.filter((pt) =>
|
||||
c.aggregates.includes(pt.id)
|
||||
);
|
||||
this.changePlotType(this.selectedPlotType);
|
||||
this.updateDateRange(c.dateRange, false);
|
||||
this.limitbycomponent.limitBy = c.limitBy;
|
||||
@@ -130,46 +167,48 @@ ngAfterViewInit(): void {
|
||||
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleGallery(event: Event){
|
||||
toggleGallery(event: Event) {
|
||||
this.galleryView.show = this.enableGallery;
|
||||
}
|
||||
}
|
||||
|
||||
loading(event: LoadingEvent) {
|
||||
this.plotJobActive = event.loading;
|
||||
}
|
||||
|
||||
updateDateRange(newDateRange: string, updatePlot=true) {
|
||||
(<HTMLInputElement>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<PlotType>) {
|
||||
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<string>){
|
||||
return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0;
|
||||
selectedPlotTypesContains(plotTypeIds: Array<string>) {
|
||||
return this.selectedPlotType.filter((pt) => plotTypeIds.includes(pt.id))
|
||||
.length > 0;
|
||||
}
|
||||
|
||||
|
||||
dateRangeAsString() : string {
|
||||
return (<HTMLInputElement>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 {
|
||||
@@ -177,12 +216,11 @@ toggleGallery(event: Event){
|
||||
}
|
||||
}
|
||||
|
||||
getAxes() : AxesTypes {
|
||||
|
||||
getAxes(): AxesTypes {
|
||||
const x = 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];
|
||||
if (!x.includes(plotType.xAxis)) {
|
||||
x.push(plotType.xAxis);
|
||||
@@ -192,43 +230,46 @@ toggleGallery(event: Event){
|
||||
}
|
||||
}
|
||||
|
||||
return new AxesTypes(x,y);
|
||||
return new AxesTypes(x, y);
|
||||
}
|
||||
|
||||
abort() {
|
||||
this.plotService.abort((<any>window).submitterId).subscribe({
|
||||
this.plotService.abort((<any> 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<string>();
|
||||
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
|
||||
this.datePicker.getDateValue(), // dateRange
|
||||
aggregates, // aggregates
|
||||
this.intervalUnit,
|
||||
this.intervalValue,
|
||||
@@ -242,24 +283,29 @@ toggleGallery(event: Event){
|
||||
|
||||
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(
|
||||
(<any>window).submitterId,
|
||||
(<any> window).submitterId,
|
||||
config,
|
||||
renderOptions
|
||||
renderOptions,
|
||||
);
|
||||
return request;
|
||||
}
|
||||
|
||||
serializedConfig(): string {
|
||||
try{
|
||||
try {
|
||||
const config = this.createPlotConfig();
|
||||
return JSON.stringify(config);
|
||||
}catch (e) {
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,79 +25,99 @@ public class DateTimeRangeParser {
|
||||
|
||||
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);
|
||||
|
||||
if (matcher.matches()) {
|
||||
|
||||
final String beginEnd = matcher.group("beginEnd");
|
||||
final boolean begin = "B".equals(beginEnd);
|
||||
final String amountString = matcher.group("amount");
|
||||
|
||||
OffsetDateTime result = offsetTime;
|
||||
|
||||
final String amountUnitString = matcher.group("amountUnit");
|
||||
final Pattern regexAmountUnit = Pattern.compile("(?<amount>\\-?[0-9]*)(?<unit>[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);
|
||||
final String unitString = matcher.group("unit");
|
||||
|
||||
switch (unitString) {
|
||||
case "m": {
|
||||
final ChronoUnit unit = ChronoUnit.MINUTES;
|
||||
if (begin) {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
||||
result = result.plus(amount, unit).truncatedTo(unit);
|
||||
} else {
|
||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "H": {
|
||||
final ChronoUnit unit = ChronoUnit.HOURS;
|
||||
if (begin) {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
||||
result = result.plus(amount, unit).truncatedTo(unit);
|
||||
} else {
|
||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "D": {
|
||||
final ChronoUnit unit = ChronoUnit.DAYS;
|
||||
if (begin) {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(unit);
|
||||
result = result.plus(amount, unit).truncatedTo(unit);
|
||||
} else {
|
||||
return offsetTime.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
|
||||
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
|
||||
// because
|
||||
// DayOfWeek
|
||||
// goes from 1
|
||||
// to 7
|
||||
final ChronoUnit unit = ChronoUnit.WEEKS;
|
||||
if (begin) {
|
||||
return offsetTime.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
|
||||
result = result.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
|
||||
.truncatedTo(ChronoUnit.DAYS);
|
||||
} else {
|
||||
return offsetTime.plus(amount, unit).with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
|
||||
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) {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
|
||||
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
|
||||
} else {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1)
|
||||
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) {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
|
||||
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
|
||||
} else {
|
||||
return offsetTime.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1)
|
||||
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1)
|
||||
.plus(1, ChronoUnit.YEARS).minusSeconds(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + unitString);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("invalid input: " + timeDefinition);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.lucares.pdb.plot.api;
|
||||
|
||||
public class DateValue {
|
||||
public enum DateType {
|
||||
QUICK, RELATIVE, ABSOLUTE
|
||||
}
|
||||
|
||||
private DateType type;
|
||||
|
||||
private String display;
|
||||
|
||||
private String value;
|
||||
|
||||
public DateType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(final DateType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public void setDisplay(final String display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(final String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import org.lucares.utils.Preconditions;
|
||||
|
||||
public class PlotSettings {
|
||||
|
||||
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);
|
||||
}
|
||||
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 + "]";
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Arguments> providerDatePeriods_multiple() {
|
||||
|
||||
return Stream.of(//
|
||||
|
||||
//
|
||||
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", "2024-03-29 12:00:00"),
|
||||
|
||||
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/EH15m", "2024-03-29 10:45:00", "2024-03-29 13:14:59")
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("providerDatePeriods_multiple")
|
||||
public void testDatePeriods_multiple(final String now, final String datePeriod, final String expectedStart,
|
||||
final String expectedEnd) throws Exception {
|
||||
final OffsetDateTime offsetTime = LocalDateTime.parse(now, PlotSettings.DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
||||
|
||||
final DateTimeRange actual = DateTimeRangeParser.parse(offsetTime, datePeriod);
|
||||
|
||||
final String actualStart = PlotSettings.DATE_FORMAT.format(actual.getStart());
|
||||
final String actualEnd = PlotSettings.DATE_FORMAT.format(actual.getEnd());
|
||||
System.out.println("at " + now + " " + datePeriod + " -> " + actualStart + " - " + actualEnd);
|
||||
Assertions.assertEquals(expectedStart, actualStart, "start");
|
||||
Assertions.assertEquals(expectedEnd, actualEnd, "end");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import 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<Aggregate> 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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user