dashboard #1

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

View File

@@ -38,6 +38,7 @@ import { AddTextDialogComponent } from './dashboard-page/dashboard/add-text-dial
import { TextWidgetComponent } from './dashboard-page/dashboard/text-widget/text-widget.component'; import { TextWidgetComponent } from './dashboard-page/dashboard/text-widget/text-widget.component';
import { AddPlotDialogComponent } from './dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component'; import { AddPlotDialogComponent } from './dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component';
import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot-widget.component'; import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot-widget.component';
import { FullScreenPlotDialogComponent } from './dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -61,7 +62,8 @@ import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot
AddTextDialogComponent, AddTextDialogComponent,
TextWidgetComponent, TextWidgetComponent,
AddPlotDialogComponent, AddPlotDialogComponent,
PlotWidgetComponent PlotWidgetComponent,
FullScreenPlotDialogComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material/dialog';
import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Dashboard, DashboardService, PlotSize, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service'; import { Dashboard, DashboardService, PlotSize, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service';
import { PlotConfig, PlotRequest, PlotResponse, PlotService } from 'src/app/plot.service'; import { PlotConfig, PlotRequest, PlotResponse, PlotService, RenderOptions } from 'src/app/plot.service';
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component'; import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component'; import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
@@ -67,11 +67,15 @@ export class DashboardComponent implements OnInit {
} }
} }
createPlotRequest(plotWidget: PlotWidget): PlotRequest { createPlotRequest(plotWidget: PlotWidget): PlotRequest {
const height = this.height(plotWidget.size); const height = this.height(plotWidget.size);
const width = this.width(plotWidget.size); const width = this.width(plotWidget.size);
const fullWidth = window.innerWidth-30;
const fullHeight = window.innerHeight-30;
const request = new PlotRequest( const request = new PlotRequest(
height, height,
width, width,
@@ -80,7 +84,12 @@ export class DashboardComponent implements OnInit {
false, // keyOutside false, // keyOutside
false, // generateThumbnail false, // generateThumbnail
(<any>window).submitterId+crypto.randomUUID(), (<any>window).submitterId+crypto.randomUUID(),
plotWidget.config); plotWidget.config,
{
'main': new RenderOptions(height,width, false, true),
'fullScreen': new RenderOptions(fullHeight,fullWidth, false, true)
}
);
return request; return request;
} }

View File

@@ -0,0 +1,11 @@
<style>
button {
position: absolute;
top: 1rem;
right: 1rem;
}
</style>
<img [src]="data.imageUrl" (click)="close()" />
<button mat-icon-button mat-dialog-close cdkFocusInitial><img src="assets/img/close.svg" class="icon-small" /></button>

View File

@@ -0,0 +1,16 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-full-screen-plot-dialog',
templateUrl: './full-screen-plot-dialog.component.html'
})
export class FullScreenPlotDialogComponent {
constructor(public dialogRef: MatDialogRef<void>, @Inject(MAT_DIALOG_DATA) public data: {imageUrl: string}){}
close(): void {
this.dialogRef.close();
}
}

View File

@@ -14,8 +14,8 @@
} }
</style> </style>
<div class="dashboard-card" [ngClass]="{'size-medium' : data.widget.size=='MEDIUM'}"> <div class="dashboard-card" [ngClass]="{'size-medium' : data.widget.size=='MEDIUM'}">
<mat-spinner *ngIf="!data.plotResponse?.imageUrl && !isError"></mat-spinner> <mat-spinner *ngIf="!hasRender('main') && !isError"></mat-spinner>
<img *ngIf="data.plotResponse?.imageUrl" src="{{data.plotResponse?.imageUrl}}" /> <img *ngIf="hasRender('main')" [src]="getImageUrl('main')" (click)="showFullScreenImage()" />
<div *ngIf="isError"> <div *ngIf="isError">
There was an error! This is a good time to panic! There was an error! This is a good time to panic!
</div> </div>

View File

@@ -1,7 +1,9 @@
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service'; import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service';
import { PlotViewComponent } from 'src/app/plot-view/plot-view.component'; import { PlotViewComponent } from 'src/app/plot-view/plot-view.component';
import { PlotRequest, PlotResponse, PlotService } from 'src/app/plot.service'; import { PlotRequest, PlotResponse, PlotService, RenderOptions } from 'src/app/plot.service';
import { FullScreenPlotDialogComponent } from '../full-screen-plot-dialog/full-screen-plot-dialog.component';
@Component({ @Component({
selector: 'app-plot-widget', selector: 'app-plot-widget',
@@ -17,37 +19,28 @@ export class PlotWidgetComponent implements AfterViewInit {
@ViewChild("plotView") plotView!: PlotViewComponent; @ViewChild("plotView") plotView!: PlotViewComponent;
constructor(private plotService : PlotService){} constructor(private dialog: MatDialog, ){}
ngAfterViewInit(): void { ngAfterViewInit(): void {
/*
const plotRequest = this.createPlotRequest();
this.plotService.sendPlotRequest(plotRequest).subscribe({
next: (response: PlotResponse) => {
this.thumbnailUrl = response.imageUrl;
},
error: () => {
this.isError = true;
}
});
*/
} }
createPlotRequest(): PlotRequest { hasRender(name: string): boolean{
return this.data.plotResponse!.rendered[name] !== undefined;
}
getImageUrl(name: string ): string {
return this.data.plotResponse!.rendered[name];
}
showFullScreenImage(){
const height = window.innerHeight - 20; this.dialog.open(FullScreenPlotDialogComponent,{
const width = window. innerWidth - 20; width: 'calc(100% - 15px)',
height: 'calc(100% - 15px)',
const request = new PlotRequest( 'data': {'imageUrl': this.getImageUrl('fullScreen')}
500, }).afterClosed().subscribe(() => {
600,
600, // thumbnailMaxWidth });
500, // thumbnailMaxHeight }
false, // keyOutside
false, // generateThumbnail
(<any>window).submitterId,
this.data.widget.config!);
return request;
}
} }

View File

@@ -281,7 +281,8 @@ export class PlotViewComponent implements OnInit {
false, // keyOutside false, // keyOutside
false, // generateThumbnail false, // generateThumbnail
(<any>window).submitterId, (<any>window).submitterId,
this.config!); this.config!,
{});
return request; return request;
} }

View File

@@ -205,6 +205,14 @@ export class AutocompleteResult{
constructor(public proposals: Array<Suggestion>){} constructor(public proposals: Array<Suggestion>){}
} }
type RenderOptionsMap = {
[key: string]: RenderOptions;
};
type RenderedImages = {
[key: string]: string;
};
export class PlotRequest { export class PlotRequest {
constructor( constructor(
public height : number, public height : number,
@@ -214,7 +222,8 @@ export class PlotRequest {
public keyOutside : boolean = false, public keyOutside : boolean = false,
public generateThumbnail : boolean, public generateThumbnail : boolean,
public submitterId: string, public submitterId: string,
public config: PlotConfig public config: PlotConfig,
public renders: RenderOptionsMap
){} ){}
@@ -237,6 +246,14 @@ export class PlotConfig {
public renderBarChartTickLabels: boolean = false,) {} public renderBarChartTickLabels: boolean = false,) {}
} }
export class RenderOptions {
constructor(
public height: number,
public width: number,
public keyOutside: boolean,
public renderLabels: boolean) {}
}
export class YAxisDefinition { export class YAxisDefinition {
constructor( constructor(
public axisScale : string, public axisScale : string,
@@ -249,7 +266,8 @@ export class PlotResponse {
constructor( constructor(
public imageUrl : string, public imageUrl : string,
public stats : PlotResponseStats, public stats : PlotResponseStats,
public thumbnailUrl : string){} public thumbnailUrl : string,
public rendered: RenderedImages){}
} }
export class PlotResponseStats { export class PlotResponseStats {

View File

@@ -204,7 +204,8 @@ export class VisualizationPageComponent implements OnInit {
false, // keyOutside false, // keyOutside
this.enableGallery, // generateThumbnail this.enableGallery, // generateThumbnail
(<any>window).submitterId, (<any>window).submitterId,
config); config,
{});
return request; return request;
} }
} }

View File

@@ -2,17 +2,25 @@ package org.lucares.recommind.logs;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map;
public class PlotResult { public class PlotResult {
private final Path imagePath; private final Path imagePath;
private final List<DataSeries> dataSeries; private final List<DataSeries> dataSeries;
private final Path thumbnail; private final Path thumbnail;
private final Map<String, Path> renderedImages;
public PlotResult(final Path imagePath, final List<DataSeries> dataSeries, final Path thumbnail) { public PlotResult(final Path imagePath, final List<DataSeries> dataSeries, final Path thumbnail,
final Map<String, Path> renderedImages) {
super(); super();
this.imagePath = imagePath; this.imagePath = imagePath;
this.dataSeries = dataSeries; this.dataSeries = dataSeries;
this.thumbnail = thumbnail; this.thumbnail = thumbnail;
this.renderedImages = renderedImages;
}
public Map<String, Path> getRenderedImages() {
return renderedImages;
} }
public Path getImageName() { public Path getImageName() {

View File

@@ -8,9 +8,12 @@ import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -26,6 +29,7 @@ import org.lucares.pdb.api.Tags;
import org.lucares.pdb.plot.api.AggregatorCollection; import org.lucares.pdb.plot.api.AggregatorCollection;
import org.lucares.pdb.plot.api.Limit; import org.lucares.pdb.plot.api.Limit;
import org.lucares.pdb.plot.api.PlotSettings; import org.lucares.pdb.plot.api.PlotSettings;
import org.lucares.pdb.plot.api.RenderOptions;
import org.lucares.performance.db.PerformanceDb; import org.lucares.performance.db.PerformanceDb;
import org.lucares.utils.file.FileUtils; import org.lucares.utils.file.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -145,7 +149,26 @@ public class Plotter {
thumbnail = null; thumbnail = null;
} }
return new PlotResult(outputFile, dataSeries, thumbnail); final Map<String, Path> renderedImages = new HashMap<>();
for (final Entry<String, RenderOptions> renders : plotSettings.getRenders().entrySet()) {
final Path file = Files.createTempFile(outputDir, "", ".png");
renderedImages.put(renders.getKey(), file);
final RenderOptions renderOptions = renders.getValue();
final Gnuplot gnuplot = new Gnuplot(tmpBaseDir);
final GnuplotSettings gnuplotSettings = new GnuplotSettings(file);
gnuplotSettings.setHeight(renderOptions.getHeight());
gnuplotSettings.setWidth(renderOptions.getWidth());
gnuplotSettings.setDateTimeRange(plotSettings.dateRange());
gnuplotSettings.setY1(plotSettings.getY1());
gnuplotSettings.setY2(plotSettings.getY2());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
gnuplotSettings.setKeyOutside(renderOptions.isKeyOutside());
gnuplotSettings.renderLabels(renderOptions.isRenderLabels());
gnuplot.plot(gnuplotSettings, dataSeries);
}
return new PlotResult(outputFile, dataSeries, thumbnail, renderedImages);
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
throw new AbortException(); throw new AbortException();

View File

@@ -1,11 +1,15 @@
package org.lucares.pdbui; package org.lucares.pdbui;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
@@ -120,8 +124,16 @@ public class PdbController implements HardcodedValues, PropertyKeys {
? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName() ? WEB_IMAGE_OUTPUT_PATH + "/" + result.getThumbnailName()
: "img/no-thumbnail.png"; : "img/no-thumbnail.png";
final Map<String, String> rendered = new HashMap<>();
for (final Entry<String, Path> renderedImageEntry : result.getRenderedImages().entrySet()) {
final String url = WEB_IMAGE_OUTPUT_PATH + "/"
+ renderedImageEntry.getValue().getFileName();
rendered.put(renderedImageEntry.getKey(), url);
}
final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries()); final PlotResponseStats stats = PlotResponseStats.fromDataSeries(result.getDataSeries());
final PlotResponse plotResponse = new PlotResponse(stats, imageUrl, thumbnailUrl); final PlotResponse plotResponse = new PlotResponse(stats, imageUrl, thumbnailUrl, rendered);
return ResponseEntity.ok().body(plotResponse); return ResponseEntity.ok().body(plotResponse);
} catch (final NoDataPointsException e) { } catch (final NoDataPointsException e) {

View File

@@ -1,14 +1,23 @@
package org.lucares.pdbui.domain; package org.lucares.pdbui.domain;
import java.util.Map;
public class PlotResponse { public class PlotResponse {
private String imageUrl = ""; private String imageUrl = "";
private PlotResponseStats stats; private PlotResponseStats stats;
private String thumbnailUrl; private String thumbnailUrl;
private final Map<String, String> rendered;
public PlotResponse(final PlotResponseStats stats, final String imageUrl, final String thumbnailUrl) { public PlotResponse(final PlotResponseStats stats, final String imageUrl, final String thumbnailUrl,
final Map<String, String> rendered) {
this.stats = stats; this.stats = stats;
this.imageUrl = imageUrl; this.imageUrl = imageUrl;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.rendered = rendered;
}
public Map<String, String> getRendered() {
return rendered;
} }
public String getImageUrl() { public String getImageUrl() {