The legend ("key" in Gnuplot speak) is no longer part of the image.
Instead it is a floating&movable overlay.
In the gallery we still use the legend/key in the image.
298 lines
8.5 KiB
TypeScript
298 lines
8.5 KiB
TypeScript
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<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("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);
|
|
}
|
|
|
|
getTagFields(): Observable<Array<string>> {
|
|
return this.http.get<Array<string>>('//'+window.location.hostname+':'+window.location.port+'/api/fields');
|
|
}
|
|
|
|
autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable<AutocompleteResult>
|
|
{
|
|
const options = {
|
|
params: new HttpParams()
|
|
.set('caretIndex', ""+caretIndex)
|
|
.set('query', query)
|
|
.set('resultMode', resultMode)
|
|
};
|
|
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)
|
|
}
|
|
|
|
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);
|
|
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<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(
|
|
map(
|
|
(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) {
|
|
|
|
}
|
|
|
|
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++){
|
|
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<DataType>;
|
|
y : Array<DataType>;
|
|
|
|
constructor(x: Array<DataType>, y : Array<DataType>) {
|
|
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<Suggestion>){}
|
|
}
|
|
|
|
export type RenderOptionsMap = {
|
|
[key: string]: RenderOptions;
|
|
};
|
|
|
|
export type RenderedImages = {
|
|
[key: string]: string;
|
|
};
|
|
|
|
export class PlotRequest {
|
|
constructor(
|
|
public submitterId: string,
|
|
public config: PlotConfig,
|
|
public renders: RenderOptionsMap
|
|
){}
|
|
|
|
|
|
copy(): PlotRequest {
|
|
return JSON.parse(JSON.stringify(this));
|
|
}
|
|
}
|
|
|
|
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>,
|
|
public intervalUnit: string,
|
|
public intervalValue: number,
|
|
public renderBarChartTickLabels: boolean = false,) {}
|
|
}
|
|
|
|
export class RenderOptions {
|
|
constructor(
|
|
public height: number,
|
|
public width: number,
|
|
public showKey: boolean,
|
|
public renderLabels: boolean) {}
|
|
}
|
|
|
|
export class YAxisDefinition {
|
|
constructor(
|
|
public axisScale : string,
|
|
public rangeMin : number,
|
|
public rangeMax : number,
|
|
public rangeUnit : string){}
|
|
}
|
|
|
|
export class PlotResponse {
|
|
constructor(
|
|
public stats : PlotResponseStats,
|
|
public rendered: RenderedImages){}
|
|
}
|
|
|
|
export class PlotResponseStats {
|
|
constructor(
|
|
public maxValue : number,
|
|
public values : number,
|
|
public average : number,
|
|
public plottedValues : number,
|
|
public maxAvgRatio: number,
|
|
public dataSeriesStats : Array<DataSeriesStats>){}
|
|
}
|
|
|
|
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<string, number>){}
|
|
}
|
|
|
|
export class DashTypeAndColor {
|
|
constructor(
|
|
public color: string,
|
|
public pointType: number) {}
|
|
}
|
|
|
|
export class FilterDefaults {
|
|
constructor(
|
|
public groupBy: Array<string>,
|
|
public fields: Array<string>,
|
|
public splitBy: string){}
|
|
}
|
|
|
|
export enum ResultMode {
|
|
CUT_AT_DOT = "CUT_AT_DOT",
|
|
FULL_VALUES = "FULL_VALUES"
|
|
}
|