import { Injectable, OnInit } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class PlotService { plotTypes: Array; constructor(private http: HttpClient) { this.plotTypes = new Array(); this.plotTypes.push(new PlotType("SCATTER","Scatter","scatter-chart2",true,DataType.Time,DataType.Duration)); this.plotTypes.push(new PlotType("CUM_DISTRIBUTION", "Cumulative Distribution", "cumulative-distribution-chart", true, DataType.Percent, DataType.Duration)); this.plotTypes.push(new PlotType("HISTOGRAM", "Histogram", "histogram", true, DataType.HistogramBin, DataType.HistogramCount)); this.plotTypes.push(new PlotType("PARALLEL", "Parallel Requests", "parallel-requests-chart", true, DataType.Time, DataType.Count)); this.plotTypes.push(new PlotType("BAR", "Bar", "bar-chart", true, DataType.Group, DataType.Count)); this.plotTypes.push(new PlotType("HEATMAP", "Heatmap", "heatmap", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("CONTOUR", "Contour", "contour-chart", false, DataType.Time, DataType.Duration)); this.plotTypes.push(new PlotType("RIDGELINES", "Ridgelines", "ridgelines", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("QQ", "Quantile-Quantile", "quantile-quantile", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("VIOLIN", "Violin", "violin-chart", false, DataType.Group, DataType.Duration)); this.plotTypes.push(new PlotType("STRIP", "Strip", "strip-chart", false, DataType.Group, DataType.Duration)); this.plotTypes.push(new PlotType("PIE", "Pie", "pie-chart", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("STEP_FIT", "Step Fit", "step-fit", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("LAG", "Lag", "lag-plot", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("ACF", "ACF", "acf-plot", false, DataType.Other, DataType.Other)); } getPlotTypes(): Array { return this.plotTypes.filter(plotType => plotType.active); } getTagFields(): Observable> { return this.http.get>('//'+window.location.hostname+':'+window.location.port+'/api/fields'); } autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable { const options = { params: new HttpParams() .set('caretIndex', ""+caretIndex) .set('query', query) .set('resultMode', resultMode) }; return this.http.get('//'+window.location.hostname+':'+window.location.port+'/api/autocomplete', options); } sendPlotRequest(plotRequest: PlotRequest): Observable{ //console.log("send plot request: "+ JSON.stringify(plotRequest)); const result = this.http.post('//'+window.location.hostname+':'+window.location.port+'/api/plots', plotRequest); 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){ maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average); } } response.stats.maxAvgRatio = maxAvgRatio; return response; } getFilterDefaults(): Observable{ return this.http.get('//'+window.location.hostname+':'+window.location.port+'/api/filters/defaults') } splitQuery(query: string, splitBy:string) : Observable>{ const q = "("+query+") and "+splitBy+"="; return this.autocomplete(q, q.length+1, ResultMode.FULL_VALUES).pipe( map( (autocompleteResult: AutocompleteResult) => autocompleteResult.proposals.map((suggestion:Suggestion) => suggestion.value) ) ); } } export class PlotType { id: string; name: string; icon: string active: boolean; xAxis: DataType; yAxis: DataType; constructor(id: string, name: string, icon: string, active: boolean, xAxis: DataType, yAxis: DataType) { this.id = id; this.name = name; this.icon = icon; this.active = active; this.xAxis = xAxis; this.yAxis = yAxis; } compatible(others: Array) : boolean { var xAxisTypes = new Set([this.xAxis]); var yAxisTypes = new Set([this.yAxis]); for(var i = 0; i < others.length; i++){ var other = others[i]; xAxisTypes.add(other.xAxis); yAxisTypes.add(other.yAxis); } return xAxisTypes.size <= 2 && yAxisTypes.size <= 2; } } export class TagField { name: string; constructor(name: string) { this.name = name; } } export enum DataType { Time, Duration, Percent, Count, Group, Metric, HistogramBin, HistogramCount, Other } export class AxesTypes { x : Array; y : Array; constructor(x: Array, y : Array) { this.x = x; this.y = y; } hasXAxis(type : DataType){ return this.x.includes(type); } 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]; } return undefined; } /** * 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]; } return undefined; } toString() { const x1 = this.getXAxisDataType(1); const y1 = this.getYAxisDataType(1); const x2 = this.getXAxisDataType(2); const y2 = this.getYAxisDataType(2); return (x1 ? "x1:"+DataType[x1] : "") + (y1 ? " y1:"+DataType[y1] : "") + (x2 ? " x2:"+DataType[x2] : "") + (y2 ? " y2:"+DataType[y2] : ""); } } export class Suggestion { constructor( public value: string, public newQuery: string, public newCaretPosition: number){} } export class AutocompleteResult{ constructor(public proposals: Array){} } export class PlotRequest { constructor( public query : string, public height : number, public width : number, public thumbnailMaxWidth : number = 300, public thumbnailMaxHeight : number = 200, public groupBy : Array, public limitBy : string, public limit : number, public y1:YAxisDefinition, public y2:YAxisDefinition|undefined, public dateRange : string, public aggregates : Array, public keyOutside : boolean = false, public generateThumbnail : boolean, public intervalUnit: string, public intervalValue: number, public renderBarChartTickLabels: boolean = false){} copy(): PlotRequest { return JSON.parse(JSON.stringify(this)); } } export class YAxisDefinition { constructor( public axisScale : string, public rangeMin : number, public rangeMax : number, public rangeUnit : string){} } export class PlotResponse { constructor( public imageUrl : string, public stats : PlotResponseStats, public thumbnailUrl : string){} } export class PlotResponseStats { constructor( public maxValue : number, public values : number, public average : number, public plottedValues : number, public maxAvgRatio: number, public dataSeriesStats : Array){} } export class DataSeriesStats { constructor( public name: string, public values : number, public maxValue : number, public average : number , public plottedValues : number, public dashTypeAndColor: DashTypeAndColor, public percentiles: Map){} } export class DashTypeAndColor { constructor( public color: string, public pointType: number) {} } export class FilterDefaults { constructor( public groupBy: Array, public fields: Array, public splitBy: string){} } export enum ResultMode { CUT_AT_DOT = "CUT_AT_DOT", FULL_VALUES = "FULL_VALUES" }