add filters for gallery

This commit is contained in:
2019-11-17 18:59:08 +01:00
parent 70fb84d634
commit 771f61a597
9 changed files with 227 additions and 17 deletions

View File

@@ -22,7 +22,7 @@ import { YAxisRangeComponent } from './y-axis-range/y-axis-range.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 { PlotViewComponent } from './plot-view/plot-view.component'; import { PlotViewComponent } from './plot-view/plot-view.component';
import { GalleryViewComponent, GalleryItemView } 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';
@NgModule({ @NgModule({
@@ -38,6 +38,7 @@ import { ImageToggleComponent } from './image-toggle/image-toggle.component';
PlotViewComponent, PlotViewComponent,
GalleryViewComponent, GalleryViewComponent,
GalleryItemView, GalleryItemView,
GalleryFilterView,
ImageToggleComponent ImageToggleComponent
], ],
imports: [ imports: [

View File

@@ -0,0 +1,26 @@
<mat-form-field class="pdb-form-wide">
<mat-label>Filter By:</mat-label>
<mat-select [(value)]="filterBy">
<mat-option>none</mat-option>
<mat-option value="MAX_VALUE">max value</mat-option>
<mat-option value="AVERAGE">average</mat-option>
<mat-option value="VALUES">#values</mat-option>
<mat-option value="PLOTTED_VALUES">#plotted values</mat-option>
</mat-select>
</mat-form-field>
<pdb-image-toggle *ngIf="filterBy" images="{{compareImages}}" (valueChanged)="comparatorChanged($event)"></pdb-image-toggle>
<mat-form-field *ngIf="filterBy" class="pdb-form-number-long">
<input matInput type="number" placeholder="" min="0" [(ngModel)]="value">
</mat-form-field>
<mat-form-field *ngIf="filterBy == 'MAX_VALUE' || filterBy == 'AVERAGE'" class="pdb-form-mid">
<mat-select [(value)]="unit">
<mat-option value="MILLISECONDS">millis</mat-option>
<mat-option value="SECONDS">seconds</mat-option>
<mat-option value="MINUTES">minutes</mat-option>
<mat-option value="HOURS">hours</mat-option>
<mat-option value="DAYS">days</mat-option>
</mat-select>
</mat-form-field>

View File

@@ -13,9 +13,11 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div class="pdb-form-icon-small">
<pdb-image-toggle images="{{ascDescImages}}" (valueChanged)="sortOrderChanged($event)"></pdb-image-toggle> <pdb-image-toggle images="{{ascDescImages}}" (valueChanged)="sortOrderChanged($event)"></pdb-image-toggle>
</div>
<pdb-gallery-filter-view (valueChanged)="filterChanged($event)"></pdb-gallery-filter-view>
</div> </div>
<div <div
@@ -23,6 +25,6 @@
id="gallery" id="gallery"
class="card-grid-300"> class="card-grid-300">
<pdb-gallery-item-view <pdb-gallery-item-view
*ngFor="let galleryItem of galleryItems" [data]="galleryItem"> *ngFor="let galleryItem of filteredSortedGalleryItems()" [data]="galleryItem">
</pdb-gallery-item-view> </pdb-gallery-item-view>
</div> </div>

View File

@@ -1,7 +1,93 @@
import { Component, OnInit, Input } 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 } from '../plot.service'; import { PlotService, PlotRequest, PlotResponse, PlotResponseStats } from '../plot.service';
export class GalleryFilterData {
filterBy :string;
comparator :string;
value : number;
unit: string;
constructor(filterBy :string, comparator :string, value : number, unit: string){
this.filterBy = filterBy;
this.comparator = comparator;
this.value = value;
this.unit = unit;
}
}
@Component({
selector: 'pdb-gallery-filter-view',
templateUrl: './gallery-filter-view.component.html',
styleUrls: ['./gallery-filter-view.component.scss']
})
export class GalleryFilterView {
compareImages = JSON.stringify([
{
value: 'GREATER_EQUAL',
text: '≥',
title: 'greater or equal'
},
{
value: 'LESS_EQUAL',
text: '≤',
title: 'less or equal'
}
]);
_filterBy = 'MAX_VALUE';
_comparator = 'GREATER_EQUAL';
_value : number = 0;
_unit: string = 'SECONDS';
@Output()
valueChanged : EventEmitter<GalleryFilterData> = new EventEmitter<GalleryFilterData>();
comparatorChanged(newComparator: string){
this._comparator = newComparator;
this.valueChanged.emit(undefined);
}
set filterBy(filterBy: string){
this._filterBy = filterBy;
this.valueChanged.emit(undefined);
}
get filterBy() : string{
return this._filterBy;
}
set unit(unit: string){
this._unit = unit;
this.valueChanged.emit(undefined);
}
get unit() : string{
return this._unit;
}
set comparator(comparator: string){
this._comparator = comparator;
this.valueChanged.emit(undefined);
}
get comparator() : string{
return this._comparator;
}
set value(value: number){
this._value = value;
this.valueChanged.emit(undefined);
}
get value() : number{
return this._value;
}
}
@Component({ @Component({
selector: 'pdb-gallery-view', selector: 'pdb-gallery-view',
templateUrl: './gallery-view.component.html', templateUrl: './gallery-view.component.html',
@@ -12,20 +98,26 @@ export class GalleryViewComponent implements OnInit {
galleryItems: Array<GalleryItem> = new Array<GalleryItem>(); galleryItems: Array<GalleryItem> = new Array<GalleryItem>();
show = false; show = false;
splitByValuesQueue = new Array<string>(); splitByValuesQueue = new Array<string>();
_sortBy= "NAME"; _sortBy= "NAME";
sortOrder = 0; sortOrder = 'ASC';
@ViewChild(GalleryFilterView, {static: false})
filter : GalleryFilterView;
ascDescImages = JSON.stringify([ ascDescImages = JSON.stringify([
{ {
value: 'ASC',
imageUrl: 'assets/img/ascending-filter.svg', imageUrl: 'assets/img/ascending-filter.svg',
title: 'ascending' title: 'ascending'
}, },
{ {
value: 'DESC',
imageUrl: 'assets/img/descending-filter.svg', imageUrl: 'assets/img/descending-filter.svg',
title: 'descending' title: 'descending'
} }
@@ -44,8 +136,17 @@ export class GalleryViewComponent implements OnInit {
ngOnInit() { ngOnInit() {
} }
filteredSortedGalleryItems(): Array<GalleryItem> {
return this.galleryItems.filter(item => item.show);
}
sortAndFilterGallery(){
this.filterGallery();
this.sortGallery();
}
sortGallery() { sortGallery() {
const orderFactor = this.sortOrder == 0 ? 1 : -1; // ASC=1, DESC=-1 const orderFactor = this.sortOrder == 'ASC' ? 1 : -1;
switch (this._sortBy) { switch (this._sortBy) {
case "NAME": case "NAME":
@@ -66,6 +167,52 @@ export class GalleryViewComponent implements OnInit {
} }
} }
filterGallery(){
const that = this;
this.galleryItems.forEach(function(item){
const show = that.filterPredicate(item);
item.show = show;
});
}
filterPredicate(galleryItem: GalleryItem){
const predicate = this.filter.comparator == 'LESS_EQUAL'
? function(a, b) { return a <= b; }
: function(a, b) { return a >= b; };
const millis = this.timeUnitToMillis(this.filter.value, this.filter.unit);
switch(this.filter.filterBy){
case 'NONE':
case undefined:
return true;
case 'MAX_VALUE':
return predicate(galleryItem.stats.maxValue, millis);
case 'AVERAGE':
return predicate(galleryItem.stats.average, millis);
case 'VALUES':
return predicate(galleryItem.stats.values, this.filter.value);
case 'PLOTTED_VALUES':
return predicate(galleryItem.stats.plottedValues, this.filter.value);
}
throw "unhandled option: " + this.filter.filterBy;
}
timeUnitToMillis(value, unit)
{
switch(unit){
case 'MILLISECONDS':
return value;
case 'SECONDS':
return value*1000;
case 'MINUTES':
return value*1000*60;
case 'HOURS':
return value*1000*60*60;
case 'DAYS':
return value*1000*60*60*24;
}
throw "unknown unit: " + unit;
}
renderGallery(request: PlotRequest, splitByField: string) { renderGallery(request: PlotRequest, splitByField: string) {
const that=this; const that=this;
@@ -103,7 +250,7 @@ export class GalleryViewComponent implements OnInit {
plotResponse.imageUrl = "http://"+window.location.hostname+':8080/'+plotResponse.imageUrl; plotResponse.imageUrl = "http://"+window.location.hostname+':8080/'+plotResponse.imageUrl;
let galleryItem = new GalleryItem(splitByValue, plotResponse); let galleryItem = new GalleryItem(splitByValue, plotResponse);
that.galleryItems.push(galleryItem); that.galleryItems.push(galleryItem);
that.sortGallery(); that.sortAndFilterGallery();
that.renderGalleryRecursively(masterRequest, splitByField); that.renderGalleryRecursively(masterRequest, splitByField);
}, },
error => { error => {
@@ -113,14 +260,18 @@ export class GalleryViewComponent implements OnInit {
set sortBy(name: string) { set sortBy(name: string) {
this._sortBy = name; this._sortBy = name;
this.sortGallery(); this.sortAndFilterGallery();
} }
get sortBy(): string { return this._sortBy; } get sortBy(): string { return this._sortBy; }
sortOrderChanged(event){ sortOrderChanged(event){
this.sortOrder = event; this.sortOrder = event;
this.sortGallery(); this.sortAndFilterGallery();
}
filterChanged(event){
this.sortAndFilterGallery();
} }
} }
@@ -160,11 +311,13 @@ export class GalleryItemView {
} }
export class GalleryItem { export class GalleryItem {
thumbnailUrl: string; thumbnailUrl: string;
imageUrl: string; imageUrl: string;
stats: PlotResponseStats; stats: PlotResponseStats;
splitByValue : string; splitByValue : string;
show : boolean;
constructor(splitByValue: string, plotResponse: PlotResponse){ constructor(splitByValue: string, plotResponse: PlotResponse){
this.thumbnailUrl = plotResponse.thumbnailUrl; this.thumbnailUrl = plotResponse.thumbnailUrl;

View File

@@ -1 +1,10 @@
<img src="{{imageUrl()}}" (click)="toggle($event)" class="icon-small" title="{{title()}}" /> <img
*ngIf="imageUrl()"
src="{{imageUrl()}}"
(click)="toggle($event)"
class="icon-small"
title="{{title()}}" />
<div
class="image-toggle-text"
*ngIf="text"
(click)="toggle($event)" >{{text}}</div>

View File

@@ -0,0 +1,11 @@
:host{
cursor: pointer;
display: inline-block;
vertical-align: middle;
margin-right: 1ex;
}
.image-toggle-text{
font-size: 1.5em;
font-weight: normal;
}

View File

@@ -10,29 +10,33 @@ export class ImageToggleComponent implements OnInit {
@Input() @Input()
images : string = ""; images : string = "";
value: number = 0; index: number = 0;
@Output() @Output()
valueChanged : EventEmitter<number> = new EventEmitter<number>(); valueChanged : EventEmitter<number> = new EventEmitter<number>();
text = undefined;
_states : Array<any>; _states : Array<any>;
constructor() { } constructor() { }
ngOnInit() { ngOnInit() {
this._states = JSON.parse(this.images); this._states = JSON.parse(this.images);
this.text = this._states[this.index].text;
} }
imageUrl() : string { imageUrl() : string {
return this._states[this.value].imageUrl; return this._states[this.index].imageUrl;
} }
title() : string { title() : string {
return this._states[this.value].title; return this._states[this.index].title;
} }
toggle(event){ toggle(event){
this.value = (this.value+1) % this._states.length; this.index = (this.index+1) % this._states.length;
this.valueChanged.emit(this.value); this.text = this._states[this.index].text;
this.valueChanged.emit(this._states[this.index].value);
} }
} }

View File

@@ -93,6 +93,9 @@ mat-form-field:last-child {
mat-form-field.pdb-form-number { mat-form-field.pdb-form-number {
width: 3.5em; width: 3.5em;
} }
mat-form-field.pdb-form-number-long {
width: 7em;
}
.pdb-form-icon-small { .pdb-form-icon-small {
display: inline-block; display: inline-block;
width: 2em; width: 2em;