extract plot detail view into its own component

This commit is contained in:
2020-11-01 09:15:01 +01:00
parent 50c9e56f2e
commit 08328ec1e5
15 changed files with 201 additions and 118 deletions

View File

@@ -24,6 +24,7 @@ import {MatTooltipModule} from '@angular/material/tooltip';
import { YAxisDefinitionComponent } from './y-axis-definition/y-axis-definition.component'; import { YAxisDefinitionComponent } from './y-axis-definition/y-axis-definition.component';
import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component'; import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component';
import { LimitByComponent } from './limit-by/limit-by.component'; import { LimitByComponent } from './limit-by/limit-by.component';
import { PlotDetailsComponent } from './plot-details/plot-details.component';
import { PlotViewComponent } from './plot-view/plot-view.component'; import { PlotViewComponent } from './plot-view/plot-view.component';
import { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component'; import { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component';
import { ImageToggleComponent } from './image-toggle/image-toggle.component'; import { ImageToggleComponent } from './image-toggle/image-toggle.component';
@@ -38,6 +39,7 @@ import { ImageToggleComponent } from './image-toggle/image-toggle.component';
YAxisDefinitionComponent, YAxisDefinitionComponent,
QueryAutocompleteComponent, QueryAutocompleteComponent,
LimitByComponent, LimitByComponent,
PlotDetailsComponent,
PlotViewComponent, PlotViewComponent,
GalleryViewComponent, GalleryViewComponent,
GalleryItemView, GalleryItemView,

View File

@@ -8,47 +8,18 @@
<span *ngIf="data.stats.plottedValues != data.stats.values" title="plotted values, some values might be missing due to y-axis range limits">{{data.stats.plottedValues}}/</span> <span *ngIf="data.stats.plottedValues != data.stats.values" title="plotted values, some values might be missing due to y-axis range limits">{{data.stats.plottedValues}}/</span>
<span title="total number of values in the plot">{{data.stats.values}}</span> <span title="total number of values in the plot">{{data.stats.values}}</span>
</div> </div>
<div class="fieldStatsMaxValue">Max value: <span class="time">{{ formatMs(data.stats.maxValue) }}</span></div> <div class="fieldStatsMaxValue">Max value: <span class="time">{{ utils.formatMs(data.stats.maxValue) }}</span></div>
<div class="fieldStatsAverage">Average: <span class="time">{{ formatMs(data.stats.average) }}</span></div> <div class="fieldStatsAverage">Average: <span class="time">{{ utils.formatMs(data.stats.average) }}</span></div>
</div> </div>
<table *ngIf="showDetails" class="gallery-item-details"> <pdb-plot-details *ngIf="showDetails" [stats]="data.stats"></pdb-plot-details>
<tr>
<th>Name</th>
<th>Type</th>
<th>Values</th>
<th>Avg</th>
</tr>
<tr *ngFor="let stats of data.stats.dataSeriesStats">
<td>{{ stats.name }}</td>
<td><div class="{{ pointTypeClass(stats.dashTypeAndColor) }}" title="{{ stats.name }}"></div></td>
<td>{{ stats.values }}</td>
<td>{{ formatMs(stats.average) }}</td>
</tr>
</table>
<table *ngIf="showDetails" class="gallery-item-details-matrix">
<tr>
<th></th>
<td *ngFor="let statsCol of data.stats.dataSeriesStats">
<div class="{{ pointTypeClass(statsCol.dashTypeAndColor) }}" title="{{ statsCol.name }}"></div>
</td>
</tr>
<tr *ngFor="let statsRow of data.stats.dataSeriesStats">
<td><div class="{{ pointTypeClass(statsRow.dashTypeAndColor) }}" title="{{ statsRow.name }}"></div></td>
<td *ngFor="let statsCol of data.stats.dataSeriesStats">
{{ toPercent(statsRow.average / statsCol.average) }}
</td>
</tr>
</table>
<div <div
*ngIf="showImage" *ngIf="showImage"
class="gallery-item-big-image-container" class="gallery-item-big-image-container"
> >
<img src="{{data.imageUrl}}" /> <img src="{{data.imageUrl}}" />
<div <div class="top-right">
class="gallery-item-big-image-close clickable" <!--<img class="icon-small clickable" src="assets/img/exclamation-round.svg" title="show details"/>-->
(click)="closeImage()" <img src="assets/img/close.svg" class="icon-small clickable" (click)="closeImage()" title="close" />
title="Close"
><img src="assets/img/close.svg" class="icon-small" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -45,33 +45,4 @@
right: 0; right: 0;
} }
.gallery-item-plotType {
background-image: url(/assets/img/pointTypes.png);
width: 9px;
height: 7px;
transform: scale(1.5);
}
.gallery-item-plotType_0 {background-position-x: 0px;}
.gallery-item-plotType_1 {background-position-x: -10px;}
.gallery-item-plotType_2 {background-position-x: -20px;}
.gallery-item-plotType_3 {background-position-x: -30px;}
.gallery-item-plotType_4 {background-position-x: -40px;}
.gallery-item-plotType_5 {background-position-x: -50px;}
.gallery-item-plotType_6 {background-position-x: -60px;}
.gallery-item-plotType_7 {background-position-x: -70px;}
.gallery-item-plotType_8 {background-position-x: -80px;}
.gallery-item-plotType_9 {background-position-x: -90px;}
.gallery-item-plotType_10 {background-position-x:-100px;}
.gallery-item-plotType_11 {background-position-x:-110px;}
.gallery-item-plotType_12 {background-position-x:-120px;}
.gallery-item-plotType_0051c2 {background-position-y: 0px;}
.gallery-item-plotType_bf8300 {background-position-y: -8px;}
.gallery-item-plotType_9400d3 {background-position-y: -16px;}
.gallery-item-plotType_00c254 {background-position-y: -24px;}
.gallery-item-plotType_e6e600 {background-position-y: -32px;}
.gallery-item-plotType_e51e10 {background-position-y: -40px;}
.gallery-item-plotType_57a1c2 {background-position-y: -48px;}
.gallery-item-plotType_bd36c2 {background-position-y: -56px;}

View File

@@ -1,6 +1,7 @@
import { Component, OnInit, Input, Output, ViewChild, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, ViewChild, EventEmitter } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { PlotService, PlotRequest, PlotResponse, PlotResponseStats, DashTypeAndColor } from '../plot.service'; import { PlotService, PlotRequest, PlotResponse, PlotResponseStats, DashTypeAndColor } from '../plot.service';
import { UtilService } from '../utils.service';
export class GalleryFilterData { export class GalleryFilterData {
filterBy :string; filterBy :string;
@@ -312,37 +313,7 @@ export class GalleryItemView {
showImage: boolean= false; showImage: boolean= false;
constructor(public utils: UtilService){}
formatMs(valueInMs):string {
const ms = Math.floor(valueInMs % 1000);
const s = Math.floor((valueInMs / 1000) % 60);
const m = Math.floor((valueInMs / (60*1000)) % 60);
const h = Math.floor(valueInMs / (3600*1000));
var result = "";
if (h != 0) {
result += h+"h ";
}
if (h!= 0 || m != 0) {
result += m+"m ";
}
if (h!= 0 || m != 0 || s != 0) {
result += s+"s ";
result += ms+"ms";
} else {
result += ms+"ms";
}
return result;
}
pointTypeClass(typeAndColor: DashTypeAndColor): string {
return "gallery-item-plotType gallery-item-plotType_"+typeAndColor.pointType+" gallery-item-plotType_"+typeAndColor.color.toLocaleLowerCase();
}
toPercent(percentage: number) : string{
return (Math.round(percentage * 10000) / 100)+"%";
}
openImage() { openImage() {
this.showImage=true; this.showImage=true;

View File

@@ -0,0 +1,28 @@
<table class="gallery-item-details">
<tr>
<th>Name</th>
<th>Type</th>
<th>Values</th>
<th>Avg</th>
</tr>
<tr *ngFor="let stat of stats.dataSeriesStats">
<td>{{ stat.name }}</td>
<td><div class="{{ pointTypeClass(stat.dashTypeAndColor) }}" title="{{ stat.name }}"></div></td>
<td>{{ stat.values }}</td>
<td>{{ utils.formatMs(stats.average) }}</td>
</tr>
</table>
<table class="gallery-item-details-matrix">
<tr>
<th></th>
<th *ngFor="let statsCol of stats.dataSeriesStats">
<div class="{{ pointTypeClass(statsCol.dashTypeAndColor) }}" title="{{ statsCol.name }}"></div>
</th>
</tr>
<tr *ngFor="let statsRow of stats.dataSeriesStats">
<td><div class="{{ pointTypeClass(statsRow.dashTypeAndColor) }}" title="{{ statsRow.name }}"></div></td>
<td *ngFor="let statsCol of stats.dataSeriesStats">
{{ utils.toPercent(statsRow.average / statsCol.average) }}
</td>
</tr>
</table>

View File

@@ -0,0 +1,30 @@
.plot-details-plotType {
background-image: url(/assets/img/pointTypes.png);
width: 9px;
height: 7px;
transform: scale(1.5);
}
.plot-details-plotType_0 {background-position-x: 0px;}
.plot-details-plotType_1 {background-position-x: -10px;}
.plot-details-plotType_2 {background-position-x: -20px;}
.plot-details-plotType_3 {background-position-x: -30px;}
.plot-details-plotType_4 {background-position-x: -40px;}
.plot-details-plotType_5 {background-position-x: -50px;}
.plot-details-plotType_6 {background-position-x: -60px;}
.plot-details-plotType_7 {background-position-x: -70px;}
.plot-details-plotType_8 {background-position-x: -80px;}
.plot-details-plotType_9 {background-position-x: -90px;}
.plot-details-plotType_10 {background-position-x:-100px;}
.plot-details-plotType_11 {background-position-x:-110px;}
.plot-details-plotType_12 {background-position-x:-120px;}
.plot-details-plotType_0051c2 {background-position-y: 0px;}
.plot-details-plotType_bf8300 {background-position-y: -8px;}
.plot-details-plotType_9400d3 {background-position-y: -16px;}
.plot-details-plotType_00c254 {background-position-y: -24px;}
.plot-details-plotType_e6e600 {background-position-y: -32px;}
.plot-details-plotType_e51e10 {background-position-y: -40px;}
.plot-details-plotType_57a1c2 {background-position-y: -48px;}
.plot-details-plotType_bd36c2 {background-position-y: -56px;}

View File

@@ -0,0 +1,24 @@
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, ɵpublishDefaultGlobalUtils } from '@angular/core';
import { DashTypeAndColor, PlotResponseStats } from '../plot.service';
import { UtilService } from '../utils.service';
@Component({
selector: 'pdb-plot-details',
templateUrl: './plot-details.component.html',
styleUrls: ['./plot-details.component.scss']
})
export class PlotDetailsComponent {
@Input()
stats: PlotResponseStats;
constructor(public utils: UtilService){
}
pointTypeClass(typeAndColor: DashTypeAndColor): string {
return "plot-details-plotType"
+" plot-details-plotType_"+typeAndColor.pointType
+" plot-details-plotType_"+typeAndColor.color.toLocaleLowerCase();
}
}

View File

@@ -1,5 +1,7 @@
<div <div
*ngIf="imageUrl" *ngIf="imageUrl">
<div
*ngIf="!showStats"
[style.cursor]="imageCursor" [style.cursor]="imageCursor"
(mousedown)="dragStart($event)" (mousedown)="dragStart($event)"
(mousemove)="dragging($event)" (mousemove)="dragging($event)"
@@ -10,6 +12,14 @@
id="result-image" id="result-image"
src="{{imageUrl}}" src="{{imageUrl}}"
/> />
<div class="top-right">
<img
id="plot-view-info"
class="icon-small clickable"
(click)="showDetails()"
src="assets/img/exclamation-round.svg"
title="show details" />
</div>
<div <div
id="zoom-in-slider" id="zoom-in-slider"
[style.display]="zoomInSliderStyleDisplay" [style.display]="zoomInSliderStyleDisplay"
@@ -18,4 +28,15 @@
[style.left]="zoomInSliderStyleLeft" [style.left]="zoomInSliderStyleLeft"
[style.width]="zoomInSliderStyleWidth" [style.width]="zoomInSliderStyleWidth"
></div> ></div>
</div>
<div *ngIf="showStats" class="plot-view-overlay">
<pdb-plot-details [stats]="stats"></pdb-plot-details>
<div class="top-right">
<img
class="icon-small clickable"
(click)="hideDetails()"
src="assets/img/close.svg"
title="close" />
</div>
</div>
</div> </div>

View File

@@ -9,3 +9,14 @@ img {
background: #ccc; background: #ccc;
opacity:0.4; opacity:0.4;
} }
.plot-view-overlay {
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
background-color: white;
box-shadow: 5px 5px 10px 0px #e0e0e0;
}

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { DataType, AxesTypes } from '../plot.service'; import { DataType, AxesTypes, PlotResponseStats } from '../plot.service';
@Component({ @Component({
selector: 'pdb-plot-view', selector: 'pdb-plot-view',
@@ -16,6 +16,7 @@ export class PlotViewComponent implements OnInit {
imageUrl : string; imageUrl : string;
stats : PlotResponseStats;
axes: AxesTypes; axes: AxesTypes;
@@ -38,6 +39,8 @@ export class PlotViewComponent implements OnInit {
zoomInSliderStyleLeft = "0"; zoomInSliderStyleLeft = "0";
zoomInSliderStyleWidth = "0"; zoomInSliderStyleWidth = "0";
showStats = false;
constructor() { } constructor() { }
ngOnInit() { ngOnInit() {
@@ -175,6 +178,14 @@ export class PlotViewComponent implements OnInit {
this.zoomWithDateAnchor.emit(new DateAnchor(cursorPercentOfDateRange, zoomFactor)); this.zoomWithDateAnchor.emit(new DateAnchor(cursorPercentOfDateRange, zoomFactor));
} }
} }
showDetails() {
this.showStats = true;
}
hideDetails() {
this.showStats = false;
}
} }
export class SelectionRange { export class SelectionRange {

View File

@@ -31,10 +31,6 @@ export class PlotService {
this.plotTypes.push(new PlotType("ACF", "ACF", "acf-plot", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("ACF", "ACF", "acf-plot", false, DataType.Other, DataType.Other));
} }
ngOnInit() {
}
getPlotTypes(): Array<PlotType> { getPlotTypes(): Array<PlotType> {
return this.plotTypes.filter(plotType => plotType.active); return this.plotTypes.filter(plotType => plotType.active);
} }

View File

@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UtilService {
constructor() {
}
formatMs(valueInMs):string {
const ms = Math.floor(valueInMs % 1000);
const s = Math.floor((valueInMs / 1000) % 60);
const m = Math.floor((valueInMs / (60*1000)) % 60);
const h = Math.floor(valueInMs / (3600*1000));
var result = "";
if (h != 0) {
result += h+"h ";
}
if (h!= 0 || m != 0) {
result += m+"m ";
}
if (h!= 0 || m != 0 || s != 0) {
result += s+"s ";
result += ms+"ms";
} else {
result += ms+"ms";
}
return result;
}
toPercent(percentage: number) : string{
return (Math.round(percentage * 10000) / 100)+"%";
}
}

View File

@@ -65,4 +65,3 @@
#plot-button-bar { #plot-button-bar {
text-align: right; text-align: right;
} }

View File

@@ -105,6 +105,7 @@ export class VisualizationPageComponent implements OnInit {
gallery(){ gallery(){
const that = this; const that = this;
this.plotView.imageUrl = ''; this.plotView.imageUrl = '';
that.plotView.stats = null;
that.galleryView.show=true; that.galleryView.show=true;
const request = this.createPlotRequest(); const request = this.createPlotRequest();
this.galleryView.renderGallery(request, this.splitBy.name); this.galleryView.renderGallery(request, this.splitBy.name);
@@ -132,6 +133,7 @@ export class VisualizationPageComponent implements OnInit {
const that = this; const that = this;
that.plotView.imageUrl = ''; that.plotView.imageUrl = '';
that.plotView.stats = null;
this.plotView.axes = this.getAxes(); this.plotView.axes = this.getAxes();
console.log(JSON.stringify(this.getAxes())); console.log(JSON.stringify(this.getAxes()));
that.galleryView.show=false; that.galleryView.show=false;
@@ -142,10 +144,12 @@ export class VisualizationPageComponent implements OnInit {
this.plotService.sendPlotRequest(request).subscribe(function(plotResponse){ this.plotService.sendPlotRequest(request).subscribe(function(plotResponse){
console.log("response: " + JSON.stringify(plotResponse)); console.log("response: " + JSON.stringify(plotResponse));
that.plotView.imageUrl = "http://"+window.location.hostname+':'+window.location.port+'/'+plotResponse.imageUrl; that.plotView.imageUrl = "http://"+window.location.hostname+':'+window.location.port+'/'+plotResponse.imageUrl;
that.plotView.stats = plotResponse.stats;
document.dispatchEvent(new Event("invadersPause", {})); document.dispatchEvent(new Event("invadersPause", {}));
}, },
error => { error => {
that.plotView.imageUrl = ''; that.plotView.imageUrl = '';
that.plotView.stats = null;
that.showError(error.error.message); that.showError(error.error.message);
document.dispatchEvent(new Event("invadersPause", {})); document.dispatchEvent(new Event("invadersPause", {}));
}); });

View File

@@ -105,6 +105,12 @@ a.external-link:after {
cursor: pointer; cursor: pointer;
} }
.top-right {
position: absolute;
top: 0;
right: 0;
}
body .mat-select-panel, body .mat-autocomplete-panel { body .mat-select-panel, body .mat-autocomplete-panel {
max-height: 300px; max-height: 300px;
} }