Files
perfdb/pdb-js/src/app/plot.service.ts
Andreas Huber 43e13b53b1 make the legend movable
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.
2023-09-30 17:12:49 +02:00

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