Compare commits
8 Commits
686e3edd60
...
bacd86d836
| Author | SHA1 | Date | |
|---|---|---|---|
| bacd86d836 | |||
| c7af333052 | |||
| a3aa62aee2 | |||
| 6d2e8da805 | |||
| 53e7a21602 | |||
| b6045eda22 | |||
| 6d8af4fdc6 | |||
| 359c17bf29 |
@@ -1,6 +1,6 @@
|
||||
package org.lucares.pdb.map;
|
||||
|
||||
import java.util.List;
|
||||
import org.lucares.utils.HumanBytes;
|
||||
|
||||
public class PersistentMapStats {
|
||||
private long values = 0;
|
||||
@@ -87,7 +87,7 @@ public class PersistentMapStats {
|
||||
builder.append(String.format("\navg. depth= %.2f", averageDepth));
|
||||
builder.append(String.format("\navg. fill= %.2f", averageFill));
|
||||
builder.append(String.format("\nvalues/node=%.2f", averageValuesInNode));
|
||||
builder.append(String.format("\nfile size= %s\n", toHumanBytes(fileSize)));
|
||||
builder.append(String.format("\nfile size= %s\n", HumanBytes.toHumanBytes(fileSize)));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -100,21 +100,9 @@ public class PersistentMapStats {
|
||||
builder.append(String.format("\navg. depth= %.2f -> %.2f", old.averageDepth, averageDepth));
|
||||
builder.append(String.format("\navg. fill= %.2f -> %.2f", old.averageFill, averageFill));
|
||||
builder.append(String.format("\nvalues/node=%.2f -> %.2f", old.averageValuesInNode, averageValuesInNode));
|
||||
builder.append(String.format("\nfile size= %s -> %s\n", toHumanBytes(old.fileSize), toHumanBytes(fileSize)));
|
||||
builder.append(String.format("\nfile size= %s -> %s\n", HumanBytes.toHumanBytes(old.fileSize),
|
||||
HumanBytes.toHumanBytes(fileSize)));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String toHumanBytes(final long bytes) {
|
||||
final List<String> powers = List.of("bytes", "KB", "MB", "GB", "TB", "PB", "EB");
|
||||
|
||||
int power = 1;
|
||||
String result = String.format("%d bytes", bytes);
|
||||
while (bytes >= Math.pow(1024, power) && power < powers.size()) {
|
||||
result = String.format("%.3f", bytes / Math.pow(1024, power));
|
||||
result = result.replaceAll("\\.?0*$", "");
|
||||
result = result + " " + powers.get(power);
|
||||
power = power + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.lucares.pdb.api;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AbortException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 7614132985675048490L;
|
||||
@@ -29,4 +31,18 @@ public class AbortException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
|
||||
public static void sleepAbortibly(final long millis) throws AbortException {
|
||||
final long deadline = System.currentTimeMillis() + millis;
|
||||
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(Math.min(10, deadline - System.currentTimeMillis()));
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new AbortException();
|
||||
}
|
||||
AbortException.abortIfInterrupted();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
"scripts": [
|
||||
"node_modules/marked/marked.min.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
||||
1356
pdb-js/package-lock.json
generated
@@ -23,7 +23,9 @@
|
||||
"@angular/platform-browser": "^15.0.2",
|
||||
"@angular/platform-browser-dynamic": "^15.0.2",
|
||||
"@angular/router": "^15.0.2",
|
||||
"marked": "^4.2.12",
|
||||
"moment": "^2.29.1",
|
||||
"ngx-markdown": "^15.1.2",
|
||||
"rxjs": "~7.5.0",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"tslib": "^2.3.0",
|
||||
@@ -34,6 +36,7 @@
|
||||
"@angular/cli": "^15.0.2",
|
||||
"@angular/compiler-cli": "^15.0.2",
|
||||
"@types/jasmine": "~3.10.0",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^12.11.1",
|
||||
"jasmine-core": "~3.10.0",
|
||||
"karma": "~6.3.0",
|
||||
|
||||
@@ -47,6 +47,7 @@ import { CustomizableGridComponent } from './customizable-grid/customizable-grid
|
||||
import {DragDropModule} from '@angular/cdk/drag-drop';
|
||||
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
||||
import { FocusDirective } from './focus.directive';
|
||||
import { MarkdownModule } from 'ngx-markdown';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -77,6 +78,7 @@ import { FocusDirective } from './focus.directive';
|
||||
FocusDirective
|
||||
],
|
||||
imports: [
|
||||
MarkdownModule.forRoot(),
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
max-height: unset;
|
||||
}
|
||||
</style>
|
||||
<h1 mat-dialog-title>{{data.title}}</h1>
|
||||
|
||||
<pdb-visualization-page mat-dialog-content #plot [defaultConfig]="data.config" [galleryEnabled]="false"></pdb-visualization-page>
|
||||
|
||||
<div mat-dialog-actions align="end">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<style>
|
||||
mat-form-field textarea {
|
||||
height: 7em;
|
||||
height: 20em;
|
||||
}
|
||||
</style>
|
||||
<h1 mat-dialog-title>Add Text</h1>
|
||||
@@ -9,6 +9,7 @@
|
||||
<mat-label>Text</mat-label>
|
||||
<textarea matInput [(ngModel)]="text" #textElement focus></textarea>
|
||||
</mat-form-field>
|
||||
<div>Text field supports <a href="https://spec.commonmark.org/" target="_blank" rel="noopener">Markdown</a>.</div>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close (click)="close()">Cancel</button>
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editable-hovered {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.editable:hover .editable-hovered{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
.handle {
|
||||
display: block;
|
||||
height: 1.5em;
|
||||
|
||||
@@ -40,7 +40,8 @@ export class DashboardComponent implements OnInit {
|
||||
this.repairArrangement();
|
||||
|
||||
dashboard.plots.forEach(p => {
|
||||
this.plotWidgetRenderData.push(new PlotWidgetRenderData(p));
|
||||
const submitterId = (<any>window).submitterId + (<any>window).randomId();
|
||||
this.plotWidgetRenderData.push(new PlotWidgetRenderData(p, submitterId));
|
||||
});
|
||||
|
||||
this.loadImages(0, this.plotWidgetRenderData);
|
||||
@@ -64,17 +65,25 @@ export class DashboardComponent implements OnInit {
|
||||
loadImages(index: number, plotWidgetQueue: PlotWidgetRenderData[]) {
|
||||
|
||||
if (index < plotWidgetQueue.length){
|
||||
|
||||
const plot = plotWidgetQueue[index];
|
||||
const request = PlotWidget.createPlotRequest(plot.widget);
|
||||
this.plotService.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
plot.plotResponse= response;
|
||||
},
|
||||
error: (error:any)=> {},
|
||||
complete: () => {
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
}
|
||||
});
|
||||
if (plot.isAborted) {
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
}else{
|
||||
const request = PlotWidget.createPlotRequest(plot.widget, plot.submitterId);
|
||||
this.plotService.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
plot.plotResponse= response;
|
||||
},
|
||||
error: (error:any)=> {
|
||||
plot.error = error;
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
},
|
||||
complete: () => {
|
||||
this.loadImages(index +1 , plotWidgetQueue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +128,7 @@ export class DashboardComponent implements OnInit {
|
||||
|
||||
addPlot() {
|
||||
this.dialog.open(AddPlotDialogComponent,{
|
||||
data: {title: "Add Plot"},
|
||||
data: {},
|
||||
width: 'calc(100% - 1em)',
|
||||
height: 'calc(100% - 1em)'
|
||||
}).afterClosed().subscribe((config: PlotConfig | "") => {
|
||||
@@ -127,7 +136,7 @@ export class DashboardComponent implements OnInit {
|
||||
const widget = new PlotWidget((<any>window).randomId(), 'MEDIUM', config);
|
||||
this.dashboard!.plots.push(widget);
|
||||
this.dashboard!.arrangement[0].push(widget.id);
|
||||
this.plotWidgetRenderData.push(new PlotWidgetRenderData(widget));
|
||||
this.plotWidgetRenderData.push(new PlotWidgetRenderData(widget, (<any>window).randomId()));
|
||||
this.loadImages(this.plotWidgetRenderData.length-1, this.plotWidgetRenderData);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
width: 402px;
|
||||
height: 302px;
|
||||
}
|
||||
img {
|
||||
img.render-img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
@@ -38,16 +38,51 @@
|
||||
.dashboard-card:hover .editable-hovered {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.aborted-img {
|
||||
flex-grow: 0.3;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.aborted-img img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.invader {
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAUCAYAAACTQC2+AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kKGRAxBENShygAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAbUlEQVRIx9WVQQrAMAgEM6X///L2FCihYvRg7F416LCsQdKo0DWKZA4CBGzjev1lRHgezS0lkan3IYr485ZFdo5oJZkbWoRWfSWrJ8p6suvZucsgCS8THsHX+zKiO5uLaO763bpobvoS/e6HfQBzIE0PhAsDxgAAAABJRU5ErkJggg==);
|
||||
width: 26px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
.spinner {
|
||||
animation: wobble 2s linear infinite;
|
||||
}
|
||||
@keyframes wobble {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="dashboard-card" [ngClass]="{'size-medium' : true}">
|
||||
<div class="editable-hovered top-right">
|
||||
<button mat-icon-button (click)="edit()" ><img src="/assets/img/edit-outline.svg"/></button>
|
||||
<button mat-icon-button (click)="delete()"><img src="/assets/img/recycle-bin-line.svg"/></button>
|
||||
</div>
|
||||
<mat-spinner *ngIf="!hasRender('main') && !isError"></mat-spinner>
|
||||
<img *ngIf="hasRender('main')" [src]="getImageUrl('main')" (click)="showFullScreenImage()" />
|
||||
<div *ngIf="isError">
|
||||
<div *ngIf="!hasRender('main') && !data?.error && !data.isAborted">
|
||||
<button mat-button (click)="abort()"><span class="invader spinner"></span>Cancel</button>
|
||||
</div>
|
||||
<img *ngIf="hasRender('main')" [src]="getImageUrl('main')" (click)="showFullScreenImage()" class="render-img" />
|
||||
<div *ngIf="data?.error && !data?.isAborted">
|
||||
There was an error! This is a good time to panic!
|
||||
</div>
|
||||
<div *ngIf="data?.isAborted" class="aborted-img">
|
||||
<img src="assets/img/image-aborted.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ export class PlotWidgetComponent {
|
||||
|
||||
public thumbnailUrl = "";
|
||||
|
||||
isError = false;
|
||||
|
||||
@ViewChild("plotView") plotView!: PlotViewComponent;
|
||||
|
||||
@@ -45,6 +44,17 @@ export class PlotWidgetComponent {
|
||||
});
|
||||
}
|
||||
|
||||
abort(){
|
||||
window.console.log("abort");
|
||||
this.data.isAborted = true;
|
||||
this.service.abort(this.data.submitterId).subscribe({
|
||||
complete: () => {
|
||||
window.console.log("cancelled");
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.dialog
|
||||
.open(ConfirmationDialogComponent, {
|
||||
@@ -60,23 +70,23 @@ export class PlotWidgetComponent {
|
||||
|
||||
edit() {
|
||||
this.dialog.open(AddPlotDialogComponent, {
|
||||
data: {config: this.data.widget.config, title:"Edit Plot"},
|
||||
data: {config: this.data.widget.config},
|
||||
width: 'calc(100% - 15px)',
|
||||
height: 'calc(100% - 15px)',
|
||||
}).afterClosed().subscribe((config?: PlotConfig) => {
|
||||
if (config !== undefined && config.query.length > 0) {
|
||||
this.data.widget.config = config;
|
||||
|
||||
this.isError = false;
|
||||
this.data.error = false;
|
||||
this.data.plotResponse = undefined;
|
||||
|
||||
const request = PlotWidget.createPlotRequest(this.data.widget);
|
||||
const request = PlotWidget.createPlotRequest(this.data.widget, this.data.submitterId);
|
||||
this.service.sendPlotRequest(request).subscribe({
|
||||
next: (response: PlotResponse)=> {
|
||||
this.data.plotResponse = response;
|
||||
},
|
||||
error: (error:any)=> {
|
||||
this.isError = true;
|
||||
this.data.error = true;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
<button mat-icon-button (click)="edit()"><img src="/assets/img/edit-outline.svg"/></button>
|
||||
<button mat-icon-button (click)="delete()"><img src="/assets/img/recycle-bin-line.svg"/></button>
|
||||
</div>
|
||||
<p *ngFor="let line of lines()">{{line}}</p>
|
||||
<markdown [data]="this.data.text"></markdown>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ export class PlotWidget extends BaseWidget {
|
||||
super(id, 'PLOT', size);
|
||||
}
|
||||
|
||||
public static createPlotRequest(widget: PlotWidget): PlotRequest {
|
||||
public static createPlotRequest(widget: PlotWidget, submitterId: string): PlotRequest {
|
||||
|
||||
const height = this.height(widget.size);
|
||||
const width = this.width(widget.size);
|
||||
@@ -77,7 +77,7 @@ export class PlotWidget extends BaseWidget {
|
||||
const fullHeight = window.innerHeight-30;
|
||||
|
||||
const request = new PlotRequest(
|
||||
(<any>window).submitterId+(<any>window).randomId(),
|
||||
submitterId,
|
||||
widget.config,
|
||||
{
|
||||
'main': new RenderOptions(height,width, false, true),
|
||||
@@ -115,7 +115,9 @@ export type PlotSize = 'SMALL'|'MEDIUM'|'LARGE';
|
||||
export type PlotType = 'TEXT'|'PLOT';
|
||||
|
||||
export class PlotWidgetRenderData {
|
||||
constructor(public widget: PlotWidget, public plotResponse?: PlotResponse) {
|
||||
public isAborted = false;
|
||||
public error: string|boolean = false;
|
||||
constructor(public widget: PlotWidget, public submitterId: string, public plotResponse?: PlotResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
<div id="filters">
|
||||
<div id="filterpanel">
|
||||
|
||||
|
||||
<mat-form-field class="pdb-form-full-width">
|
||||
<mat-label>Type:</mat-label>
|
||||
<mat-select multiple [(ngModel)]="selectedPlotType" (ngModelChange)="changePlotType($event)">
|
||||
@@ -52,7 +50,7 @@
|
||||
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
|
||||
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
|
||||
|
||||
<mat-checkbox *ngIf="galleryEnabled" [(ngModel)]="enableGallery">Gallery</mat-checkbox>
|
||||
<mat-checkbox *ngIf="galleryEnabled" [(ngModel)]="enableGallery" (click)="toggleGallery($event)">Gallery</mat-checkbox>
|
||||
|
||||
<mat-form-field *ngIf="enableGallery" class="pdb-form-full-width">
|
||||
<mat-label>Split By:</mat-label>
|
||||
@@ -66,6 +64,13 @@
|
||||
|
||||
|
||||
<div id="plot-button-bar">
|
||||
<a
|
||||
mat-icon-button
|
||||
[routerLink]="['/vis']"
|
||||
[queryParams]="{config: serializedConfig()}"
|
||||
target="_blank"
|
||||
aria-label="open new window with the same search"
|
||||
title="open new window with the same search"><img src="assets/img/link.svg" aria-hidden="true"/></a>
|
||||
<button
|
||||
*ngIf="!enableGallery && !plotJobActive"
|
||||
[disabled]="plotJobActive"
|
||||
|
||||
@@ -67,5 +67,7 @@
|
||||
}
|
||||
|
||||
#plot-button-bar {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,12 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||
plotJobActive = false;
|
||||
|
||||
constructor(private plotService: PlotService, private snackBar: MatSnackBar) {
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (!this.defaultConfig && params.get("config")) {
|
||||
const config = JSON.parse(params.get("config")!);
|
||||
this.defaultConfig = config;
|
||||
}
|
||||
}
|
||||
|
||||
showError(message:string) {
|
||||
@@ -96,8 +102,6 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||
this.plot();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
@@ -128,6 +132,10 @@ ngAfterViewInit(): void {
|
||||
}
|
||||
}
|
||||
|
||||
toggleGallery(event: Event){
|
||||
this.galleryView.show = this.enableGallery;
|
||||
}
|
||||
|
||||
loading(event: LoadingEvent) {
|
||||
this.plotJobActive = event.loading;
|
||||
}
|
||||
@@ -246,4 +254,13 @@ ngAfterViewInit(): void {
|
||||
);
|
||||
return request;
|
||||
}
|
||||
|
||||
serializedConfig(): string {
|
||||
try{
|
||||
const config = this.createPlotConfig();
|
||||
return JSON.stringify(config);
|
||||
}catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
<mat-form-field class="pdb-form-mid">
|
||||
<mat-label>Y{{yIndex}}-Axis Unit:</mat-label>
|
||||
<mat-select [(value)]="yAxisUnit">
|
||||
<mat-optgroup label="⸺numbers⸺">
|
||||
<mat-optgroup label="—numbers—">
|
||||
<mat-option value="AUTOMATIC_NUMBER">auto (number)</mat-option>
|
||||
<mat-option value="NO_UNIT">no unit</mat-option>
|
||||
</mat-optgroup>
|
||||
<mat-optgroup label="⸺time⸺">
|
||||
<mat-optgroup label="—bytes—">
|
||||
<mat-option value="AUTOMATIC_BYTES">auto (bytes)</mat-option>
|
||||
<mat-option value="BYTES">bytes</mat-option>
|
||||
</mat-optgroup>
|
||||
<mat-optgroup label="—time—">
|
||||
<mat-option value="AUTOMATIC_TIME">auto (time)</mat-option>
|
||||
<mat-option value="MILLISECONDS">millis</mat-option>
|
||||
<mat-option value="SECONDS">seconds</mat-option>
|
||||
@@ -25,11 +29,11 @@
|
||||
</mat-optgroup>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="yAxisUnit !== 'AUTOMATIC_TIME' && yAxisUnit !== 'AUTOMATIC_NUMBER'" class="pdb-form-number">
|
||||
<mat-form-field *ngIf="yAxisUnit !== 'AUTOMATIC_TIME' && yAxisUnit !== 'AUTOMATIC_NUMBER' && yAxisUnit !== 'AUTOMATIC_BYTES'" class="pdb-form-number">
|
||||
<mat-label>Min:</mat-label>
|
||||
<input matInput type="number" placeholder="Min" min="0" [(ngModel)]="minYValue">
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="yAxisUnit !== 'AUTOMATIC_TIME' && yAxisUnit !== 'AUTOMATIC_NUMBER'" class="pdb-form-number">
|
||||
<mat-form-field *ngIf="yAxisUnit !== 'AUTOMATIC_TIME' && yAxisUnit !== 'AUTOMATIC_NUMBER' && yAxisUnit !== 'AUTOMATIC_BYTES'" class="pdb-form-number">
|
||||
<mat-label>Max:</mat-label>
|
||||
<input matInput type="number" placeholder="Max" min="0" [(ngModel)]="maxYValue">
|
||||
</mat-form-field>
|
||||
|
||||
1
pdb-js/src/assets/img/bookmark-add-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24"><rect fill="none" height="24" width="24"/><path d="M17,11v6.97l-5-2.14l-5,2.14V5h6V3H7C5.9,3,5,3.9,5,5v16l7-3l7,3V11H17z M21,7h-2v2h-2V7h-2V5h2V3h2v2h2V7z"/></svg>
|
||||
|
After Width: | Height: | Size: 280 B |
1
pdb-js/src/assets/img/bookmark-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z"/></svg>
|
||||
|
After Width: | Height: | Size: 219 B |
1
pdb-js/src/assets/img/bookmarks-line.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M15 7v12.97l-4.21-1.81-.79-.34-.79.34L5 19.97V7h10m4-6H8.99C7.89 1 7 1.9 7 3h10c1.1 0 2 .9 2 2v13l2 1V3c0-1.1-.9-2-2-2zm-4 4H5c-1.1 0-2 .9-2 2v16l7-3 7 3V7c0-1.1-.9-2-2-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 311 B |
5
pdb-js/src/assets/img/image-aborted.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="-30 -30 700 700">
|
||||
<path d="M-.012 65.611h640.024v508.778H-.012V65.611zm180.026 132.273c22.996 0 41.635 18.638 41.635 41.634 0 22.997-18.638 41.635-41.635 41.635-22.996 0-41.634-18.638-41.634-41.635 0-22.996 18.638-41.634 41.634-41.634zm175.207 178.679l83.269-143.978 88.466 223.763h-412.86v-27.756l34.702-1.725 34.69-85.005 17.338 60.722h52.052l45.095-116.222 57.248 90.201zM47.528 107.764h544.944v424.47H47.528v-424.47z"/>
|
||||
|
||||
<line x1="10" y1="630" x2="630" y2="10" style="stroke:#000; stroke-width: 60px; stroke-linecap: round;" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 758 B |
1
pdb-js/src/assets/img/launch.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>
|
||||
|
After Width: | Height: | Size: 268 B |
1
pdb-js/src/assets/img/move.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-g</title><polyline points="176 112 256 32 336 112" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><line x1="255.98" y1="32" x2="256" y2="480" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><polyline points="176 400 256 480 336 400" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><polyline points="400 176 480 256 400 336" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><polyline points="112 176 32 256 112 336" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><line x1="32" y1="256" x2="480" y2="256" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
|
||||
|
After Width: | Height: | Size: 928 B |
1
pdb-js/src/assets/img/resize.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-c</title><polyline points="304 96 416 96 416 208" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><line x1="405.77" y1="106.2" x2="111.98" y2="400.02" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/><polyline points="208 416 96 416 96 304" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
|
||||
|
After Width: | Height: | Size: 532 B |
1
pdb-js/src/assets/img/save-outline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><title>ionicons-v5-p</title><path d="M380.93,57.37A32,32,0,0,0,358.3,48H94.22A46.21,46.21,0,0,0,48,94.22V417.78A46.21,46.21,0,0,0,94.22,464H417.78A46.36,46.36,0,0,0,464,417.78V153.7a32,32,0,0,0-9.37-22.63ZM256,416a64,64,0,1,1,64-64A63.92,63.92,0,0,1,256,416Zm48-224H112a16,16,0,0,1-16-16V112a16,16,0,0,1,16-16H304a16,16,0,0,1,16,16v64A16,16,0,0,1,304,192Z" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg>
|
||||
|
After Width: | Height: | Size: 542 B |
@@ -190,7 +190,8 @@ mat-form-field.pdb-form-mid {
|
||||
mat-form-field.pdb-form-wide {
|
||||
width: 10em;
|
||||
}
|
||||
pdb-visualization-page .mat-mdc-form-field-subscript-wrapper {
|
||||
pdb-visualization-page .mat-mdc-form-field-subscript-wrapper,
|
||||
app-add-text-dialog .mat-mdc-form-field-subscript-wrapper {
|
||||
display: none;/**/
|
||||
}
|
||||
|
||||
@@ -207,3 +208,23 @@ pdb-visualization-page .mat-mdc-form-field-subscript-wrapper {
|
||||
top: 0.2em;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
a[target="_blank"]:after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
background: url("assets/img/external-link.svg") no-repeat;
|
||||
background-size: 1em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-left: 0.3em;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
|
||||
/* styles for markdown*/
|
||||
markdown blockquote {
|
||||
border-left: 4px grey solid;
|
||||
display: block;
|
||||
margin-left: 2em;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
@@ -7,6 +7,8 @@ public enum RangeUnit {
|
||||
|
||||
NO_UNIT(false, Type.Number, "Value"),
|
||||
|
||||
AUTOMATIC_BYTES(true, Type.Number, "Value"),
|
||||
|
||||
BYTES(false, Type.Number, "Value"),
|
||||
|
||||
AUTOMATIC_TIME(true, Type.Duration, "Duration"),
|
||||
@@ -43,6 +45,10 @@ public enum RangeUnit {
|
||||
return type == Type.Number || type == Type.HistogramCount;
|
||||
}
|
||||
|
||||
public boolean isBytes() {
|
||||
return this == BYTES || this == AUTOMATIC_BYTES;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return axisLabel;
|
||||
}
|
||||
@@ -51,11 +57,9 @@ public enum RangeUnit {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int valueForUnit(final int value) {
|
||||
public long valueForUnit(final long value) {
|
||||
|
||||
switch (this) {
|
||||
case AUTOMATIC_NUMBER:
|
||||
return Integer.MAX_VALUE;
|
||||
case NO_UNIT:
|
||||
case BYTES:
|
||||
return value;
|
||||
@@ -69,10 +73,12 @@ public enum RangeUnit {
|
||||
return value * 60 * 60 * 1000;
|
||||
case DAYS:
|
||||
return value * 24 * 60 * 60 * 1000;
|
||||
case AUTOMATIC_NUMBER:
|
||||
case AUTOMATIC_TIME:
|
||||
return Integer.MAX_VALUE;
|
||||
case AUTOMATIC_BYTES:
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
return Integer.MAX_VALUE;
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.lucares.pdb.plot.api;
|
||||
public class YAxisDefinition {
|
||||
private AxisScale axisScale = AxisScale.LINEAR;
|
||||
|
||||
private int rangeMin = 0;
|
||||
private int rangeMax = 300;
|
||||
private long rangeMin = 0;
|
||||
private long rangeMax = 300;
|
||||
private RangeUnit rangeUnit = RangeUnit.AUTOMATIC_TIME;
|
||||
|
||||
public AxisScale getAxisScale() {
|
||||
@@ -23,7 +23,7 @@ public class YAxisDefinition {
|
||||
return rangeUnit.valueForUnit(rangeMax);
|
||||
}
|
||||
|
||||
public int getRangeMin() {
|
||||
public long getRangeMin() {
|
||||
return rangeMin;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,15 @@ public class YAxisDefinition {
|
||||
return !rangeUnit.isAutomatic() && rangeMin >= 0 && rangeMax >= 0 && rangeMin < rangeMax;
|
||||
}
|
||||
|
||||
public void setRangeMin(final int rangeMin) {
|
||||
public void setRangeMin(final long rangeMin) {
|
||||
this.rangeMin = rangeMin;
|
||||
}
|
||||
|
||||
public int getRangeMax() {
|
||||
public long getRangeMax() {
|
||||
return rangeMax;
|
||||
}
|
||||
|
||||
public void setRangeMax(final int rangeMax) {
|
||||
public void setRangeMax(final long rangeMax) {
|
||||
this.rangeMax = rangeMax;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.lucares.pdb.plot.api.YAxisDefinition;
|
||||
import org.lucares.utils.HumanBytes;
|
||||
|
||||
class YAxisTicks {
|
||||
|
||||
@@ -45,6 +46,17 @@ class YAxisTicks {
|
||||
default:
|
||||
throw new IllegalStateException("unhandled value: " + yAxisDefinition.getRangeUnit());
|
||||
}
|
||||
} else if (yAxisDefinition.getRangeUnit().isBytes()) {
|
||||
switch (yAxisDefinition.getAxisScale()) {
|
||||
case LINEAR:
|
||||
result = computeLinearYTicksByte(height, yRangeMin, yRangeMax);
|
||||
break;
|
||||
case LOG10:
|
||||
result = computeLog10YTicksByte(height, yRangeMin, yRangeMax);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected value: " + yAxisDefinition.getAxisScale());
|
||||
}
|
||||
} else {
|
||||
switch (yAxisDefinition.getAxisScale()) {
|
||||
case LINEAR:
|
||||
@@ -59,6 +71,33 @@ class YAxisTicks {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static double log2(final double d) {
|
||||
return Math.log10(d) / Math.log10(2);
|
||||
}
|
||||
|
||||
private static List<String> computeLog10YTicksByte(final int height, final long yRangeMin, final long yRangeMax) {
|
||||
|
||||
final int fontHeight = GnuplotSettings.TICKS_FONT_SIZE * 2;
|
||||
final long plotHeight = height - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
|
||||
final int heightPerPowerOf2 = (int) Math.ceil(plotHeight / log2(yRangeMax));
|
||||
|
||||
final List<String> ticsLabels = new ArrayList<>();
|
||||
|
||||
long nextFreePositionOnYAxis = 0;
|
||||
int count = 1;
|
||||
for (long v = 1; v <= yRangeMax * 10; v *= 2) // increase yRangeMax by factor 10, because Gnuplot uses the next
|
||||
// log10 value when scaling the y-axis
|
||||
{
|
||||
if (nextFreePositionOnYAxis < count * heightPerPowerOf2) {
|
||||
ticsLabels.add("\"" + HumanBytes.toHumanBytes(v) + "\" " + v);
|
||||
nextFreePositionOnYAxis = count * heightPerPowerOf2 + fontHeight;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
return ticsLabels;
|
||||
}
|
||||
|
||||
private static List<String> computeLog10YTicksTime(final int height, final long yRangeMin, final long yRangeMax) {
|
||||
|
||||
final List<String> ticsLabels = Arrays.asList(//
|
||||
@@ -100,6 +139,52 @@ class YAxisTicks {
|
||||
return ticsLabels;
|
||||
}
|
||||
|
||||
private static List<String> computeLinearYTicksByte(final long height, final long yRangeMinInMs,
|
||||
final long yRangeMaxInMs) {
|
||||
final long plotHeight = height - GnuplotSettings.GNUPLOT_TOP_BOTTOM_MARGIN;
|
||||
final long maxLabels = plotHeight / (GnuplotSettings.TICKS_FONT_SIZE * 2);
|
||||
|
||||
final long range = yRangeMaxInMs - yRangeMinInMs;
|
||||
final long rangePerLabel = roundToNextLinearByteStep(range / maxLabels);
|
||||
|
||||
final List<String> ticsLabels = new ArrayList<>();
|
||||
for (long i = yRangeMinInMs; i <= yRangeMaxInMs; i += rangePerLabel) {
|
||||
ticsLabels.add("\"" + byteToTic(i, rangePerLabel) + "\" " + i);
|
||||
}
|
||||
|
||||
return ticsLabels;
|
||||
}
|
||||
|
||||
private static long roundToNextLinearByteStep(final long stepSize) {
|
||||
final List<Long> steps = Arrays.asList(1L, 2L, 4L, 8L, 16L, 32L, 64L, 128L, 256L, 512L,
|
||||
|
||||
HumanBytes.KB, HumanBytes.KB * 5, HumanBytes.KB * 10, HumanBytes.KB * 50, HumanBytes.KB * 100,
|
||||
HumanBytes.KB * 128, HumanBytes.KB * 256, HumanBytes.KB * 512,
|
||||
|
||||
HumanBytes.MB, HumanBytes.MB * 5, HumanBytes.MB * 10, HumanBytes.MB * 50, HumanBytes.MB * 100,
|
||||
HumanBytes.MB * 128, HumanBytes.MB * 256, HumanBytes.MB * 512,
|
||||
|
||||
HumanBytes.GB, HumanBytes.GB * 5, HumanBytes.GB * 10, HumanBytes.GB * 50, HumanBytes.GB * 100,
|
||||
HumanBytes.GB * 128, HumanBytes.GB * 256, HumanBytes.GB * 512,
|
||||
|
||||
HumanBytes.TB, HumanBytes.TB * 5, HumanBytes.TB * 10, HumanBytes.TB * 50, HumanBytes.TB * 100,
|
||||
HumanBytes.TB * 128, HumanBytes.TB * 256, HumanBytes.TB * 512,
|
||||
|
||||
HumanBytes.PB, HumanBytes.PB * 5, HumanBytes.PB * 10, HumanBytes.PB * 50, HumanBytes.PB * 100,
|
||||
HumanBytes.PB * 128, HumanBytes.PB * 256, HumanBytes.PB * 512,
|
||||
|
||||
HumanBytes.EB, HumanBytes.EB * 5, HumanBytes.EB * 10, HumanBytes.EB * 50, HumanBytes.EB * 100,
|
||||
HumanBytes.EB * 128, HumanBytes.EB * 256, HumanBytes.EB * 512);
|
||||
|
||||
for (final Long step : steps) {
|
||||
if (stepSize < step) {
|
||||
return step;
|
||||
}
|
||||
}
|
||||
|
||||
return stepSize;
|
||||
}
|
||||
|
||||
private static List<String> computeLinearYTicksTime(final long height, final long yRangeMinInMs,
|
||||
final long yRangeMaxInMs) {
|
||||
|
||||
@@ -156,6 +241,10 @@ class YAxisTicks {
|
||||
return msPerLabel;
|
||||
}
|
||||
|
||||
private static String byteToTic(final long val, final double rangePerLabel) {
|
||||
return HumanBytes.toHumanBytes(val);
|
||||
}
|
||||
|
||||
private static String msToTic(final long ms, final double msPerLabel) {
|
||||
|
||||
if (ms < 1000) {
|
||||
|
||||
32
pdb-utils/src/main/java/org/lucares/utils/HumanBytes.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package org.lucares.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HumanBytes {
|
||||
|
||||
public static final long KB = 1024;
|
||||
|
||||
public static final long MB = KB * 1024;
|
||||
|
||||
public static final long GB = MB * 1024;
|
||||
|
||||
public static final long TB = GB * 1024;
|
||||
|
||||
public static final long PB = TB * 1024;
|
||||
|
||||
public static final long EB = TB * 1024;
|
||||
|
||||
public static String toHumanBytes(final long bytes) {
|
||||
final List<String> powers = List.of("bytes", "KB", "MB", "GB", "TB", "PB", "EB");
|
||||
|
||||
int power = 1;
|
||||
String result = String.format("%d bytes", bytes);
|
||||
while (bytes >= Math.pow(1024, power) && power < powers.size()) {
|
||||
result = String.format("%.3f", bytes / Math.pow(1024, power));
|
||||
result = result.replaceAll("\\.?0*$", "");
|
||||
result = result + " " + powers.get(power);
|
||||
power = power + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||