Compare commits

..

66 Commits

Author SHA1 Message Date
eb65b56b78 convert y axis definition to standalone component 2024-10-01 19:14:15 +02:00
b0415ed972 convert image to to standalone component 2024-10-01 19:09:59 +02:00
4b211e425d convert limit by to standalone component 2024-10-01 19:07:34 +02:00
9459eee606 lazy loading for main page 2024-10-01 18:56:58 +02:00
a81c458775 convert customizable grid to standalone component and add lazy loading 2024-10-01 18:55:24 +02:00
ee0eab22f8 convert help page into standalone component and add lazy loading 2024-10-01 18:47:34 +02:00
9ccb7a14b5 lazy loading for upload page 2024-10-01 18:42:52 +02:00
05cc0e985a convert upload page to standalone component 2024-10-01 18:38:44 +02:00
6dc5ba1a1e convert main page into standalone component 2024-09-29 13:51:06 +02:00
a6ae8e533e convert new dashboard component to standalone component 2024-09-29 13:11:15 +02:00
8e0572d35b convert date picker to standalone component 2024-09-29 10:10:00 +02:00
697d3664aa convert AddTextDialogComponent into standalone component 2024-09-29 09:34:56 +02:00
526e1d842e update Gradle to 8.10.2 2024-09-29 08:59:48 +02:00
06c9b4998f update Junit to 5.11.1 2024-09-29 08:57:48 +02:00
ac5dcdc58f Merge pull request 'dashboard' (#1) from dashboard into master
Reviewed-on: #1
2024-09-29 06:47:35 +00:00
296d42e721 update node to 20.17.0 2024-09-29 08:45:56 +02:00
9db020ceb0 update 3rd party libs 2024-09-29 08:21:46 +02:00
36da503be9 add date picker to dashboard page 2024-09-29 08:11:38 +02:00
42751f84d4 remove head-box dif, because it is unnecessary 2024-09-28 10:35:15 +02:00
39d7c029ea remove debug output for absolute date 2024-09-28 10:28:28 +02:00
f072185074 nicer placement of the date picker 2024-09-28 10:26:53 +02:00
122ba11a79 remove debugger 2024-09-28 10:26:38 +02:00
f1d7799bf1 update luxon to 3.5.0 2024-09-28 09:18:23 +02:00
680f1bff03 update Angular Material to 18.2 2024-09-28 09:16:56 +02:00
fa0315650a update Angular to 18.2 2024-09-28 09:14:31 +02:00
8f765dd478 remove date picker test component 2024-09-28 09:12:31 +02:00
1234560512 update ngx-markdown to be compatible with angular 2024-09-28 08:54:47 +02:00
2711579afb add resource handler to be able to load htm/js
With the Angular update the smart people at google moved the
location where the build output is written to. It is now in a
subfolder called 'browser'. This means the default lookup locations
of Spring don't work anymore.
2024-09-28 08:37:47 +02:00
3ac021e45f ng update @angular/material@18 2024-07-28 12:17:53 +02:00
c199eae4ff g update @angular/core@18 @angular/cli@18 2024-07-28 12:17:00 +02:00
6073dd0779 fix compile errors after angualr 17 update 2024-07-28 12:14:19 +02:00
fee5eda780 update ngx-markdown 2024-07-28 11:58:01 +02:00
cc0db6d732 update angular material to 17 2024-07-28 11:56:46 +02:00
f084396e95 update angular cor and cli to 17 2024-07-28 11:55:22 +02:00
e4b6eea4b1 angular updates are shit 2024-07-28 11:54:09 +02:00
75fa966af3 udpate Spring to 3.3.2 2024-07-28 11:47:05 +02:00
a3e4917e89 update gradle versions plugin to 0.51.0 2024-07-27 14:09:37 +02:00
05fc03e48f update to gradle 8.9 2024-07-27 14:05:36 +02:00
b00ce507ef update to jdk 21 2024-07-27 14:02:37 +02:00
a69fe09464 more updates to handle date ranges with my custom range language 2024-07-27 13:19:35 +02:00
77b99801e4 add projects directly instead of scanning for them
IntelliJ Idea cannot import projects unless they are listed in the
settings.gradle
2024-05-05 10:27:49 +02:00
da210145e6 opt out of analytics 2024-05-05 10:23:33 +02:00
21a84b5223 use date picker in visualization page 2024-05-05 10:22:45 +02:00
a99a884423 add date parser for relative time notation 2024-05-05 08:40:30 +02:00
6d6b6ba00c add date picker component 2024-04-25 09:41:10 +02:00
380bad6967 update testing properties 2024-04-03 16:46:25 +02:00
f3556b6909 custom ticks for no unit y-axis 2024-04-03 16:46:25 +02:00
452030de5e use more efficient way to determine needed bytes for variable byte
encoding

I experimented with a few branch-free variants, but they were slower
than just using ternary operators and readable code.
2024-04-01 12:44:30 +02:00
6b8e3d2089 add component for a custom date picker that also knows relative date ranges like 'last month' 2024-03-31 14:48:33 +02:00
b0467c4571 better scrolling of legend 2023-10-03 14:12:59 +02:00
a64e851c33 define max size for legend 2023-10-01 14:33:52 +02:00
fefc7411c1 add source-map-explorer 2023-10-01 10:31:51 +02:00
00a1bf8ffb update to Java 21 2023-10-01 09:30:36 +02:00
317d31bbda update gradle to 8.3 2023-10-01 09:21:07 +02:00
d339f5307f update node to 18.18.0 2023-10-01 09:09:40 +02:00
b3085c9b0c Merge commit '731e9264e334043d2f36bc35bfd0231447dfb8dc' into dashboard 2023-09-30 20:27:31 +02:00
96955e0515 remove moment from angular build 2023-09-30 20:26:06 +02:00
9a41f132f8 angular update to 16 step 4 2023-09-30 18:16:37 +02:00
33c4fe1448 angular update to 16 step3 2023-09-30 17:54:19 +02:00
d320ff3d93 angular update to 16 step 2 2023-09-30 17:51:40 +02:00
4ce9cca7e0 angular update to 16 step 1 2023-09-30 17:48:26 +02:00
29215f0410 update gradle node plugin 2023-09-30 17:30:08 +02:00
abc60c6de2 update antlr to 4.13.1 2023-09-30 17:26:44 +02:00
c969c5c848 update libs 2023-09-30 17:23:33 +02:00
6552d51bcc there is no liquishit in this project 2023-09-30 17:14:58 +02:00
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
56 changed files with 17267 additions and 10179 deletions

View File

@@ -4,27 +4,27 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'java'
id 'eclipse'
id 'com.github.ben-manes.versions' version "0.46.0" // check for dependency updates run: gradlew dependenyUpdates
id 'com.github.ben-manes.versions' version "0.51.0" // check for dependency updates run: gradlew dependenyUpdates
}
ext {
javaVersion=17
javaVersion=21
version_log4j2= '2.19.0' // keep in sync with spring-boot-starter-log4j2
version_spring = '3.0.4'
version_junit = '5.9.2'
version_junit_platform = '1.9.2'
version_nodejs = '16.17.1' // keep in sync with npm
version_npm = '8.15.0' // keep in sync with nodejs
version_log4j2= '2.20.0' // keep in sync with spring-boot-starter-log4j2
version_spring = '3.3.4'
version_junit = '5.11.1'
version_junit_platform = '1.11.1'
version_nodejs = '20.17.0' // keep in sync with npm
version_npm = '10.8.2' // keep in sync with nodejs
lib_antlr = "org.antlr:antlr4:4.11.1"
lib_antlr = "org.antlr:antlr4:4.13.2"
lib_commons_collections4 = 'org.apache.commons:commons-collections4:4.4'
lib_commons_csv= 'org.apache.commons:commons-csv:1.10.0'
lib_commons_lang3 = 'org.apache.commons:commons-lang3:3.12.0'
lib_jackson_databind = 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
lib_commons_csv= 'org.apache.commons:commons-csv:1.12.0'
lib_commons_lang3 = 'org.apache.commons:commons-lang3:3.17.0'
lib_jackson_databind = 'com.fasterxml.jackson.core:jackson-databind:2.18.0'
lib_log4j2_core = "org.apache.logging.log4j:log4j-core:${version_log4j2}"
lib_log4j2_slf4j_impl = "org.apache.logging.log4j:log4j-slf4j-impl:${version_log4j2}"
@@ -141,5 +141,5 @@ subprojects {
}
wrapper {
gradleVersion = '8.0.2'
gradleVersion = '8.10.2'
}

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

34
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +85,9 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,18 +134,21 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

22
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@@ -1,2 +0,0 @@
liquibase.hub.mode=off

View File

@@ -18,12 +18,15 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "build/generated/resources",
"outputPath": {
"base": "build/generated/resources"
},
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
@@ -35,7 +38,8 @@
],
"scripts": [
"node_modules/marked/marked.min.js"
]
],
"browser": "src/main.ts"
},
"configurations": {
"production": {
@@ -57,12 +61,11 @@
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
"outputHashing": "all",
"sourceMap": true
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
@@ -77,10 +80,10 @@
},
"configurations": {
"production": {
"browserTarget": "pdb-js:build:production"
"buildTarget": "pdb-js:build:production"
},
"development": {
"browserTarget": "pdb-js:build:development"
"buildTarget": "pdb-js:build:development"
}
},
"defaultConfiguration": "development"
@@ -88,7 +91,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "pdb-js:build"
"buildTarget": "pdb-js:build"
}
},
"test": {
@@ -111,5 +114,8 @@
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@@ -2,7 +2,8 @@ import java.nio.file.Files
import java.nio.file.Paths
plugins {
id("com.github.node-gradle.node") version "3.5.1"
id("com.github.node-gradle.node") version "7.0.0"
id("java-library") // not sure why this is needed - is already set in /build.gradle - but without it the project sometimes (not always) is not configured as a java project
}

24598
pdb-js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,41 +9,44 @@
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"releasebuild": "ng build --configuration production"
"releasebuild": "ng build --configuration production",
"explore": "source-map-explorer build/generated/resources/**/*.js"
},
"private": true,
"dependencies": {
"@angular/animations": "^15.0.2",
"@angular/cdk": "^15.0.1",
"@angular/common": "^15.0.2",
"@angular/compiler": "^15.0.2",
"@angular/core": "^15.0.2",
"@angular/forms": "^15.0.2",
"@angular/material": "^15.0.1",
"@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",
"@angular/animations": "^18.2.6",
"@angular/cdk": "^18.2.6",
"@angular/common": "^18.2.6",
"@angular/compiler": "^18.2.6",
"@angular/core": "^18.2.6",
"@angular/forms": "^18.2.6",
"@angular/material": "^18.2.6",
"@angular/platform-browser": "^18.2.6",
"@angular/platform-browser-dynamic": "^18.2.6",
"@angular/router": "^18.2.6",
"luxon": "^3.4.3",
"marked": "^12",
"ngx-markdown": "18.0.0",
"rxjs": "~7.5.0",
"rxjs-compat": "^6.6.7",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
"zone.js": "^0.14.10"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.0.2",
"@angular/cli": "^15.0.2",
"@angular/compiler-cli": "^15.0.2",
"@types/jasmine": "~3.10.0",
"@angular-devkit/build-angular": "^18.2.6",
"@angular/cli": "^18.2.6",
"@angular/compiler-cli": "^18.2.6",
"@types/jasmine": "~4.3.0",
"@types/luxon": "^3.3.2",
"@types/marked": "^4.0.8",
"@types/node": "^12.11.1",
"jasmine-core": "~3.10.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "4.8"
"jasmine-core": "~4.6.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"source-map-explorer": "^2.5.3",
"typescript": "^5.4.5"
}
}

View File

@@ -1,30 +1,25 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { VisualizationPageComponent } from './visualization-page/visualization-page.component';
import { MainPageComponent } from './main-page/main-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component';
import { HelpPageComponent } from './help-page/help-page.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component';
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.component";
const routes: Routes = [
{ path: '', component: MainPageComponent},
{ path: 'vis', component: VisualizationPageComponent },
{ path: 'dashboard', component: DashboardPageComponent},
{ path: 'dashboard/:id', component: DashboardComponent},
{ path: 'upload', component: UploadPageComponent },
{ path: 'grid', component: CustomizableGridComponent },
{ path: 'help', component: HelpPageComponent },
{ path: "", loadComponent: () => import("./main-page/main-page.component").then(m => m.MainPageComponent) },
{ path: "vis", component: VisualizationPageComponent },
{ path: "dashboard", component: DashboardPageComponent },
{ path: "dashboard/:id", component: DashboardComponent },
{ path: "upload", loadComponent: () => import("./upload-page/upload-page.component").then(m => m.UploadPageComponent) },
{ path: "grid", loadComponent: () => import("./customizable-grid/customizable-grid.component").then(m => m.CustomizableGridComponent) },
{ path: "help", loadComponent: () => import("./help-page/help-page.component").then(m => m.HelpPageComponent) },
// { path: '**', component: PageNotFoundComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {})
RouterModule.forRoot(routes, {}),
],
declarations: [],
exports: [RouterModule]
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -1,89 +1,90 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, enableProdMode } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { enableProdMode, NgModule } from "@angular/core";
import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MainPageComponent } from './main-page/main-page.component';
import { HelpPageComponent } from './help-page/help-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component';
import { VisualizationPageComponent } from './visualization-page/visualization-page.component';
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatSelectModule} from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import { YAxisDefinitionComponent } from './y-axis-definition/y-axis-definition.component';
import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.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 { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component';
import { ImageToggleComponent } from './image-toggle/image-toggle.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { NewDashboardComponent } from './dashboard-page/new-dashboard/new-dashboard.component';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import {MatTableModule} from '@angular/material/table';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatCardModule} from '@angular/material/card';
import {MatBadgeModule} from '@angular/material/badge';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { AddTextDialogComponent } from './dashboard-page/dashboard/add-text-dialog/add-text-dialog.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 { 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';
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component';
import { DatePickerComponent } from "./components/datepicker/date-picker.component";
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';
import { MatAutocompleteModule } from "@angular/material/autocomplete";
import { MatButtonModule } from "@angular/material/button";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatSelectModule } from "@angular/material/select";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatRadioModule } from "@angular/material/radio";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { MatTooltipModule } from "@angular/material/tooltip";
import { OverlayModule } from "@angular/cdk/overlay";
import { YAxisDefinitionComponent } from "./y-axis-definition/y-axis-definition.component";
import { QueryAutocompleteComponent } from "./query-autocomplete/query-autocomplete.component";
import { PlotDetailsComponent } from "./plot-details/plot-details.component";
import { PlotViewComponent } from "./plot-view/plot-view.component";
import {
GalleryFilterView,
GalleryItemView,
GalleryViewComponent,
} from "./gallery-view/gallery-view.component";
import { ImageToggleComponent } from "./image-toggle/image-toggle.component";
import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import {
MAT_DIALOG_DEFAULT_OPTIONS,
MatDialogModule,
} from "@angular/material/dialog";
import { MatTabsModule } from "@angular/material/tabs";
import { MatTableModule } from "@angular/material/table";
import { MatGridListModule } from "@angular/material/grid-list";
import { MatCardModule } from "@angular/material/card";
import { MatBadgeModule } from "@angular/material/badge";
import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.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 { 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({
declarations: [
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";
import { MainPageComponent } from "./main-page/main-page.component";
import { LimitByComponent } from "./limit-by/limit-by.component";
@NgModule({ declarations: [
AppComponent,
MainPageComponent,
HelpPageComponent,
UploadPageComponent,
VisualizationPageComponent,
YAxisDefinitionComponent,
QueryAutocompleteComponent,
LimitByComponent,
PlotDetailsComponent,
PlotViewComponent,
GalleryViewComponent,
GalleryItemView,
GalleryFilterView,
ImageToggleComponent,
DashboardPageComponent,
NewDashboardComponent,
DashboardComponent,
AddTextDialogComponent,
TextWidgetComponent,
AddPlotDialogComponent,
PlotWidgetComponent,
FullScreenPlotDialogComponent,
CustomizableGridComponent,
ConfirmationDialogComponent,
FocusDirective
FocusDirective,
],
bootstrap: [AppComponent],
imports: [
MarkdownModule.forRoot(),
BrowserModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
DatePickerComponent,
DragDropModule,
ImageToggleComponent,
LimitByComponent,
MainPageComponent,
MatAutocompleteModule,
MatBadgeModule,
MatButtonModule,
@@ -98,14 +99,17 @@ import { MarkdownModule } from 'ngx-markdown';
MatProgressSpinnerModule,
MatSelectModule,
MatSnackBarModule,
MatTabsModule,
MatTableModule,
MatTooltipModule,
BrowserAnimationsModule,
HttpClientModule
OverlayModule,
YAxisDefinitionComponent
],
providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],
bootstrap: [AppComponent]
})
providers: [{
provide: MAT_DIALOG_DEFAULT_OPTIONS,
useValue: { hasBackdrop: true },
}, provideHttpClient(withInterceptorsFromDi())] })
export class AppModule {}
enableProdMode()
enableProdMode();

View File

@@ -0,0 +1,188 @@
<style>
.date-picker-overlay {
width: 500px;
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
background-color: white;
}
.tab-quick {
display: flex;
flex-direction: row;
gap: 2em;
}
.tab-quick-column {
display: flex;
flex-direction: column;
}
.date-picker-form-field {
width: 23.5em;
}
</style>
<button
mat-button
matTooltip="Date Picker"
(click)="isOpen = !isOpen"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
[attr.disabled]="isDisabled ? 'disabled' : null"
>
{{ datePickerControl.value?.display }}
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
>
<div class="date-picker-overlay">
<mat-tab-group
animationDuration="0ms"
(selectedTabChange)="tabChange()"
[(selectedIndex)]="selectedTabIndex"
>
<mat-tab label="Quick">
<div class="tab-quick">
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('BD/E1D', 'today')">
Today
</button>
<button mat-button (click)="applyQuick('B-1D/E-1D', 'yesterday')">
Yesterday
</button>
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
This Week
</button>
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
This Month
</button>
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
This Year
</button>
</div>
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('B-7D/ED', 'last 7 days')">
Last 7 Days
</button>
<button mat-button (click)="applyQuick('B-1W/ED', 'last week')">
Last Week
</button>
<button mat-button (click)="applyQuick('B-30D/ED', 'last 30 days')">
Last 30 Days
</button>
<button mat-button (click)="applyQuick('B-1M/E-1M', 'last month')">
Last Month
</button>
<button
mat-button
(click)="applyQuick('B-3M/E-1M', 'last 3 months')"
>
Last 3 Months
</button>
<button mat-button (click)="applyQuick('B-1Y/E-1Y', 'last year')">
Last Year
</button>
</div>
</div>
</mat-tab>
<mat-tab label="Relative">
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'seconds', 59)"
>
<mat-label>Seconds:</mat-label>
<input
matInput
name="relative-time-range-seconds"
[(ngModel)]="relativeTimeRange.seconds"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'minutes', 59)"
>
<mat-label>Minutes:</mat-label>
<input
matInput
name="relative-time-range-minutes"
[(ngModel)]="relativeTimeRange.minutes"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'hours', 23)"
>
<mat-label>Hours:</mat-label>
<input
matInput
name="relative-time-range-hours"
[(ngModel)]="relativeTimeRange.hours"
type="number"
min="0"
max="23"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'days', 367)"
>
<mat-label>Days:</mat-label>
<input
matInput
name="relative-time-range-days"
[(ngModel)]="relativeTimeRange.days"
type="number"
min="0"
max="367"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'months', 11)"
>
<mat-label>Months:</mat-label>
<input
matInput
name="relative-time-range-months"
[(ngModel)]="relativeTimeRange.months"
type="number"
min="0"
max="11"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'years', 10)"
>
<mat-label>Years:</mat-label>
<input
matInput
name="relative-time-range-years"
[(ngModel)]="relativeTimeRange.years"
type="number"
min="0"
max="10"
/>
</mat-form-field>
<button mat-button (click)="applyRelativeTimeRange()">Apply</button>
</mat-tab>
<mat-tab label="Absolute">
<mat-form-field class="date-picker-form-field">
<mat-label>Date Range:</mat-label>
<input matInput [formControl]="dateRange" name="dates" />
</mat-form-field>
<button mat-button (click)="applyAbsoluteTime()">Apply</button>
</mat-tab>
</mat-tab-group>
</div>
</ng-template>

View File

@@ -0,0 +1,208 @@
import { OverlayModule } from "@angular/cdk/overlay";
import {
Component,
EventEmitter,
forwardRef,
Input,
Output,
} from "@angular/core";
import {
ControlValueAccessor,
FormControl,
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
Validators,
} from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import { MAT_DIALOG_DEFAULT_OPTIONS } from "@angular/material/dialog";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { MatTabsModule } from "@angular/material/tabs";
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
export type DateType = "QUICK" | "RELATIVE" | "ABSOLUTE";
export class DateValue {
constructor(
public type: DateType,
public value: string,
public display: string,
) {}
}
export class DatePickerChange {
constructor(public value: DateValue) {}
}
@Component({
selector: "app-date-picker",
templateUrl: "./date-picker.component.html",
standalone: true,
imports: [
BrowserModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatTabsModule,
FormsModule,
ReactiveFormsModule,
OverlayModule
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerComponent),
multi: true,
},
{
provide: MAT_DIALOG_DEFAULT_OPTIONS,
useValue: { hasBackdrop: true },
}
],
})
export class DatePickerComponent implements ControlValueAccessor {
isOpen = false;
relativeTimeRangeUnit = "relativeTimeRangeMinutes";
relativeTimeRangeAmount = 15;
relativeTimeRange = {
seconds: 0,
minutes: 15,
hours: 0,
days: 0,
months: 0,
years: 0,
};
dateRange = new FormControl<string>(
"2019-10-05 00:00:00 - 2019-10-11 23:59:59",
[
Validators.pattern(
/^\d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2} - \d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2}$/,
),
],
);
datePickerControl = new FormControl(
new DateValue("QUICK", "BM/EM", "this month"),
);
@Input()
isDisabled: boolean = false;
@Output()
readonly dateValueSelected: EventEmitter<DatePickerChange> = new EventEmitter<
DatePickerChange
>();
selectedTabIndex = 0;
_onChange = (_: any) => {};
_onTouched = (_: any) => {};
constructor() {}
getDateValue(): DateValue {
return this.datePickerControl.value!;
}
writeValue(obj: DateValue): void {
this.datePickerControl.setValue(obj);
switch (obj.type) {
case "QUICK":
break;
case "ABSOLUTE":
this.dateRange.setValue(obj.value);
break;
case "RELATIVE":
const x = this.relativeTimeRange;
// obj.value looks like "P1Y2M3DT4H5M6S" or "PT4H5M6S" or "P1Y2M3D" or "P1YT6S" or ...
const matches = obj.value.match(
/P(?:(\d+)Y)(?:(\d+)M)(?:(\d+)D)?(?:T(?:(\d+)H)(?:(\d+)M)(?:(\d+)S))?/,
) ?? [];
x.years = Number.parseInt(matches[1] ?? 0);
x.months = Number.parseInt(matches[2] ?? 0);
x.days = Number.parseInt(matches[3] ?? 0);
x.hours = Number.parseInt(matches[4] ?? 0);
x.minutes = Number.parseInt(matches[5] ?? 0);
x.seconds = Number.parseInt(matches[6] ?? 0);
break;
default:
}
}
registerOnChange(fn: any): void {
this._onChange = fn;
}
registerOnTouched(fn: any): void {
this._onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
dateDisplay(): string {
return this.datePickerControl.value?.display || "no date set";
}
tabChange() {
//(<any> window).initSimpleDatePicker(); // breaks form control
}
setDateValue(dateValue: DateValue) {
this.datePickerControl.setValue(dateValue);
this._onChange(dateValue);
this.dateValueSelected.emit(new DatePickerChange(dateValue));
//console.log("date value updated: ", dateValue);
}
applyQuick(value: string, display: string) {
const newValue = new DateValue("QUICK", value, display);
this.setDateValue(newValue);
this.isOpen = false;
}
private fixToRange(val: number, min: number, max: number) {
return val < min ? min : (val > max ? max : val);
}
applyRelativeTimeRange() {
const x = this.relativeTimeRange;
const years = x.years ? "-"+x.years + "Y" : "";
const months = x.months ? "-"+x.months + "M" : "";
const days = x.days ? "-"+x.days + "D" : "";
const hours = x.hours ? "-"+x.hours + "H" : "";
const minutes = x.minutes ? "-"+x.minutes + "m" : "";
const timeRange = `B${years}${months}${days}${hours}${minutes}/Bm`;
const newValue = new DateValue("RELATIVE", timeRange, timeRange);
this.setDateValue(newValue);
this.isOpen = false;
}
applyAbsoluteTime() {
const value = <string> this.dateRange.value;
const newValue = new DateValue("ABSOLUTE", value, value);
this.setDateValue(newValue);
this.isOpen = false;
}
scrollRelativeTimeRange(
event: WheelEvent,
unit: "seconds" | "minutes" | "hours" | "days" | "months" | "years",
max: number,
) {
this.relativeTimeRange[unit] = this.fixToRange(
this.relativeTimeRange[unit] + (event.deltaY > 0 ? -1 : 1),
0,
max,
);
}
}

View File

@@ -1,12 +1,18 @@
import { Component } from '@angular/core';
import { CdkDragEnter, CdkDropList, moveItemInArray, DragRef} from '@angular/cdk/drag-drop';
import { CdkDragEnter, CdkDropList, moveItemInArray, DragRef, DragDropModule} from '@angular/cdk/drag-drop';
import { AfterViewInit } from '@angular/core';
import { ViewChild } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@Component({
selector: 'app-customizable-grid',
templateUrl: './customizable-grid.component.html'
templateUrl: './customizable-grid.component.html',
standalone: true,
imports: [
BrowserModule,
DragDropModule
]
})
export class CustomizableGridComponent implements AfterViewInit {
@ViewChild(CdkDropList) placeholder!: CdkDropList;

View File

@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angular/core';
import { Component, Inject, ViewChild } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { PlotConfig } from 'src/app/plot.service';
import { VisualizationPageComponent } from 'src/app/visualization-page/visualization-page.component';

View File

@@ -1,9 +1,28 @@
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MarkdownModule } from 'ngx-markdown';
@Component({
selector: 'app-add-text-dialog',
templateUrl: './add-text-dialog.component.html'
templateUrl: './add-text-dialog.component.html',
standalone: true,
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MarkdownModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
]
})
export class AddTextDialogComponent {
text = "";

View File

@@ -3,6 +3,16 @@
width: 100%;
height: 100%;
}
.toolbar {
display: flex;
flex-direction: row;
}
.toolbar #filter-date-range{
flex-grow: 1;
display: flex;
align-items: center;
justify-content: flex-end;
}
.center {
position: absolute;
top: 50%;
@@ -37,6 +47,9 @@
flex-basis: 0;
}
.editable {
padding: 0.5em;
}
.editable-hovered {
visibility: hidden;
@@ -73,12 +86,14 @@
<button mat-button (click)="addText()">Add Text</button>
<button mat-button (click)="addPlot()">Add Plot</button>
<button class="save-button" mat-button (click)="save()" [disabled]="!isDirty()">Save</button>
<div id="filter-date-range">
Date range: <app-date-picker #datePicker (dateValueSelected)="updateDateRange($event)" ></app-date-picker>
</div>
</div>
<div class="editable">
<h1>{{dashboard.name}}<button mat-icon-button (click)="editNameAndDescription()" class="editable-hovered"><img src="/assets/img/edit-outline.svg"/></button></h1>
<p>{{dashboard.description}}</p>
</div>
<div cdkDropListGroup class="dashboard-area">
<div
cdkDropList

View File

@@ -1,6 +1,6 @@
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, ElementRef, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
@@ -9,6 +9,7 @@ import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
import { NewDashboardComponent } from '../new-dashboard/new-dashboard.component';
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
import { DatePickerChange, DatePickerComponent } from 'src/app/components/datepicker/date-picker.component';
@Component({
selector: 'app-dashboard',
@@ -24,6 +25,9 @@ export class DashboardComponent implements OnInit {
plotWidgetRenderData: PlotWidgetRenderData[] = [];
@ViewChild("datePicker")
datePicker!: DatePickerComponent;
constructor(
private route: ActivatedRoute,
private service: DashboardService,
@@ -58,6 +62,13 @@ export class DashboardComponent implements OnInit {
});
}
updateDateRange(e: DatePickerChange) {
this.plotWidgetRenderData.forEach(r => {
r.widget.config.dateRange = e.value;
});
this.loadImages(0, this.plotWidgetRenderData);
}
isDirty() {
return this.pristineDashboardJSON !== JSON.stringify(this.dashboard);
}
@@ -70,6 +81,9 @@ export class DashboardComponent implements OnInit {
if (plot.isAborted) {
this.loadImages(index +1 , plotWidgetQueue);
}else{
plot.plotResponse = undefined; // remove old image and show loading icon
const request = PlotWidget.createPlotRequest(plot.widget, plot.submitterId);
this.plotService.sendPlotRequest(request).subscribe({
next: (response: PlotResponse)=> {

View File

@@ -1,4 +1,4 @@
import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild, input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service';
@@ -18,14 +18,13 @@ export class PlotWidgetComponent {
public thumbnailUrl = "";
@ViewChild("plotView") plotView!: PlotViewComponent;
//@ViewChild("plotView") plotView!: PlotViewComponent;
@Output()
deleted : EventEmitter<string> = new EventEmitter<string>();
constructor(private dialog: MatDialog, private service: PlotService){}
hasRender(name: string): boolean{
return this.data !== undefined && this.data.plotResponse !== undefined && this.data.plotResponse?.rendered[name] !== undefined;
}

View File

@@ -1,11 +1,26 @@
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
import { DashboardCreationData } from 'src/app/dashboard.service';
import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { MatButtonModule } from '@angular/material/button';
import { OverlayModule } from '@angular/cdk/overlay';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@Component({
selector: 'app-new-dashboard',
templateUrl: './new-dashboard.component.html'
templateUrl: './new-dashboard.component.html',
standalone: true,
imports: [
BrowserModule,
FormsModule,
MatButtonModule,
MatDialogModule,
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule
]
})
export class NewDashboardComponent implements OnInit {

View File

@@ -3,7 +3,8 @@ import { Component, OnInit } from '@angular/core';
@Component({
selector: 'pdb-help-page',
templateUrl: './help-page.component.html',
styleUrls: ['./help-page.component.scss']
styleUrls: ['./help-page.component.scss'],
standalone: true
})
export class HelpPageComponent implements OnInit {

View File

@@ -1,9 +1,14 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@Component({
selector: 'pdb-image-toggle',
templateUrl: './image-toggle.component.html',
styleUrls: ['./image-toggle.component.scss']
styleUrls: ['./image-toggle.component.scss'],
standalone: true,
imports: [
BrowserModule
]
})
export class ImageToggleComponent implements OnInit {

View File

@@ -1,10 +1,25 @@
import { Component, Input} from '@angular/core';
import {FormControl} from '@angular/forms';
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormField, MatLabel } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { MatOption, MatSelect } from '@angular/material/select';
import { BrowserModule } from '@angular/platform-browser';
@Component({
selector: 'pdb-limit-by',
templateUrl: './limit-by.component.html',
styleUrls: ['./limit-by.component.scss']
styleUrls: ['./limit-by.component.scss'],
standalone: true,
imports: [
BrowserModule,
FormsModule,
MatFormField,
MatInput,
MatLabel,
MatSelect,
MatOption,
ReactiveFormsModule
]
})
export class LimitByComponent {

View File

@@ -1,15 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
selector: 'pdb-main-page',
templateUrl: './main-page.component.html',
styleUrls: ['./main-page.component.scss']
standalone: true,
imports: [
RouterLink
]
})
export class MainPageComponent implements OnInit {
constructor() { }
ngOnInit() {
}
export class MainPageComponent {
}

View File

@@ -1,33 +1,4 @@
.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;}
.gallery-item-details td {

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, ɵpublishDefaultGlobalUtils } from '@angular/core';
import { Component, Input } from '@angular/core';
import { DashTypeAndColor, PlotResponseStats, DataSeriesStats } from '../plot.service';
import { UtilService } from '../utils.service';

View File

@@ -1,3 +1,16 @@
<!---->
<div cdkDrag
[ngClass]="{'hidden': !imageUrl || showStats || dataSeries().length == 0}"
class="plot-view--legend"
[cdkDragFreeDragPosition]="legendInitialPosition">
<div cdkDragHandle></div>
<div class="plot-view--legend-content">
<ol>
<li *ngFor="let stat of dataSeries()"><div class="{{ pointTypeClass(stat.dashTypeAndColor) }}" title="{{ stat.name }}"></div>{{ stat.name }}</li>
</ol>
</div>
</div>
<div
*ngIf="imageUrl">
<div

View File

@@ -21,3 +21,60 @@ img {
box-shadow: 5px 5px 10px 0px #e0e0e0;
overflow: auto;
}
.plot-view--legend {
border: solid 1px #ccc;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 4px;
position: fixed;
z-index: 1;
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
background-color: white;
}
.plot-view--legend:active {
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.plot-view--legend div[cdkDragHandle] {
visibility: hidden;
height: 1.2rem;
flex-shrink: 0;
}
.plot-view--legend:hover div[cdkDragHandle] {
cursor: move;
visibility: visible;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAAF0lEQVQI12P4//8/AwMDhGSEUFCAUwYAJl4R8Z1D4wIAAAAASUVORK5CYII=);
}
.plot-view--legend-content {
max-height: 30em;
overflow: auto;
max-width: 60em;
overflow: auto;
resize: both;
padding-bottom: 0.5em;
}
.plot-view--legend ol {
padding-inline-start: 0.7em;
}
.plot-view--legend ol li {
list-style-type: none;
display: flex;
flex-direction: row;
align-items: center
}
.plot-view--legend ol li .plot-details-plotType{
margin-right: 0.3em;
flex-shrink: 0;
flex-grow: 0;
}

View File

@@ -1,23 +1,29 @@
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { DataType, AxesTypes, PlotResponseStats, PlotConfig, PlotService, PlotResponse, PlotRequest, RenderOptions } from '../plot.service';
import { Component, Output, EventEmitter } from '@angular/core';
import { DataType, AxesTypes, PlotResponseStats, PlotConfig, PlotService, PlotResponse, PlotRequest, RenderOptions, DataSeriesStats, DashTypeAndColor } from '../plot.service';
import { MatSnackBar } from '@angular/material/snack-bar';
import * as moment from 'moment';
//import * as moment from 'moment';
import { WidgetDimensions } from '../dashboard.service';
import { Overlay } from "@angular/cdk/overlay";
import { DateTime, Duration } from "luxon";
import { DateValue } from '../components/datepicker/date-picker.component';
import { Observable } from 'rxjs';
@Component({
selector: 'pdb-plot-view',
templateUrl: './plot-view.component.html',
styleUrls: ['./plot-view.component.scss']
})
export class PlotViewComponent implements OnInit {
export class PlotViewComponent {
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
readonly DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; // for moment-JS
readonly gnuplotLMargin = 110; // The left margin configured for gnuplot
readonly gnuplotRMargin = 110; // The right margin configured for gnuplot
readonly gnuplotTMargin = 57; // The top margin configured for gnuplot
readonly gnuplotBMargin = 76; // The bottom margin configured for gnuplot
isOpen = false;
imageUrl! : string;
stats: PlotResponseStats | null = null;
@@ -28,7 +34,7 @@ export class PlotViewComponent implements OnInit {
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
@Output()
dateRangeUpdateEvent : EventEmitter<string> = new EventEmitter<string>();
dateRangeUpdateEvent : EventEmitter<DateValue> = new EventEmitter<DateValue>();
in_drag_mode = false;
drag_start_x = 0;
@@ -47,10 +53,9 @@ export class PlotViewComponent implements OnInit {
config? : PlotConfig;
constructor(private service : PlotService, private snackBar: MatSnackBar) { }
legendInitialPosition = {x:115,y:60};
ngOnInit() {
}
constructor(private service : PlotService, private snackBar: MatSnackBar, private overlay: Overlay) { }
showError(message:string) {
@@ -177,12 +182,12 @@ export class PlotViewComponent implements OnInit {
}
setDateRange(startDate: any, endDate: any) {
const formattedStartDate = startDate.format(this.DATE_PATTERN);
const formattedEndDate = endDate.format(this.DATE_PATTERN);
const formattedStartDate = startDate.toFormat(this.DATE_PATTERN);
const formattedEndDate = endDate.toFormat(this.DATE_PATTERN);
const newDateRange = formattedStartDate+" - "+formattedEndDate;
this.dateRangeUpdateEvent.emit(newDateRange);
const newDateValue = new DateValue('ABSOLUTE', newDateRange, newDateRange);
this.dateRangeUpdateEvent.emit(newDateValue);
}
zoomRange(range: SelectionRange) {
@@ -291,18 +296,26 @@ export class PlotViewComponent implements OnInit {
* shiftDateByAnchor(dateRangeAsString, 0.20, 0.5) zooms in by 50%, so that the date that was at 20% before the zoom is still at 20% after the zoom
* shiftDateByAnchor(dateRangeAsString, 0.33, 2) zooms out by 50%, so that the date that was at 33% before the zoom is still at 33% after the zoom
*/
shiftDateByAnchor(dateRange:string, anchorInPercentOfDateRange:number, zoomFactor:number)
shiftDateByAnchor(dateValue:DateValue, anchorInPercentOfDateRange:number, zoomFactor:number)
{
const dateRangeParsed = this.parseDateRange(dateRange);
const dateRangeInSeconds = dateRangeParsed.duration.asSeconds();
const dateRangeParsed = this.parseDateRange(dateValue);
dateRangeParsed.subscribe({
next: (dataRange: DateRange) => {
const dateRangeInSeconds = Math.floor(dataRange.duration.toMillis()/1000);
const anchorTimestampInSeconds = dateRangeParsed.startDate.clone().add(Math.floor(dateRangeInSeconds*anchorInPercentOfDateRange), "seconds");
const anchorTimestampInSeconds = dataRange.startDate.plus(Math.floor(dateRangeInSeconds*anchorInPercentOfDateRange)*1000);
const newDateRangeInSeconds = dateRangeInSeconds * zoomFactor;
const newStartDate = anchorTimestampInSeconds.clone().subtract(newDateRangeInSeconds*anchorInPercentOfDateRange, "seconds");
const newEndDate = newStartDate.clone().add({seconds: newDateRangeInSeconds});;
const newStartDate = anchorTimestampInSeconds.minus(newDateRangeInSeconds*anchorInPercentOfDateRange*1000);
const newEndDate = newStartDate.plus({seconds: newDateRangeInSeconds});;
this.setDateRange(newStartDate, newEndDate);
},
error: (err: any) => {
window.console.error("failed to parse DateValue into DateRange: ", err);
}
})
}
/**
@@ -314,26 +327,47 @@ export class PlotViewComponent implements OnInit {
* shiftDate(dateRangeAsString, -0.5, -0.5) will move the range by half its size to older values
* shiftDate(dateRangeAsString, 1, 1) will move the range by its size to newer values
*/
shiftDate(dateRange: string, factorStartDate: number, factorEndDate: number)
shiftDate(dateValue: DateValue, factorStartDate: number, factorEndDate: number)
{
const dateRangeParsed = this.parseDateRange(dateRange);
const dateRangeInSeconds = dateRangeParsed.duration.asSeconds();
this.parseDateRange(dateValue).subscribe(
dateRangeParsed => {
const dateRangeInSeconds = Math.floor(dateRangeParsed.duration.toMillis()/1000);
const newStartDate = dateRangeParsed.startDate.add({seconds: dateRangeInSeconds*factorStartDate});
const newEndDate = dateRangeParsed.endDate.add({seconds: dateRangeInSeconds*factorEndDate});
const newStartDate = dateRangeParsed.startDate.plus({seconds: dateRangeInSeconds*factorStartDate});
const newEndDate = dateRangeParsed.endDate.plus({seconds: dateRangeInSeconds*factorEndDate});
this.setDateRange(newStartDate, newEndDate);
}
);
}
parseDateRange(dateValue : DateValue) : Observable<DateRange> {
return this.service.toDateRange(dateValue);
/*
.pipe(map((dateRangeAsString:string) => {
const startDate = DateTime.fromFormat(dateRangeAsString.slice(0, 19), this.DATE_PATTERN );
const endDate = DateTime.fromFormat(dateRangeAsString.slice(22, 41), this.DATE_PATTERN );
parseDateRange(dateRangeAsString : string) : DateRange {
const startDate = moment(dateRangeAsString.slice(0, 19));
const endDate = moment(dateRangeAsString.slice(22, 41));
return {
startDate: startDate,
endDate: endDate,
duration: moment.duration(endDate.diff(startDate))
duration: endDate.diff(startDate),
};
}));
*/
}
dataSeries(): Array<DataSeriesStats> {
return this.stats ? this.stats.dataSeriesStats : [];
}
pointTypeClass(typeAndColor: DashTypeAndColor): string {
return "plot-details-plotType"
+" plot-details-plotType_"+typeAndColor.pointType
+" plot-details-plotType_"+typeAndColor.color.toLocaleLowerCase();
}
}
@@ -362,7 +396,8 @@ export class LoadingEvent {
}
export class DateRange {
startDate: any;
endDate: any;
duration: any;
constructor(
public startDate: DateTime,
public endDate: DateTime,
public duration: Duration){}
}

View File

@@ -1,64 +1,228 @@
import { Injectable, OnInit } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Injectable, OnInit } from "@angular/core";
import { HttpClient, HttpParams } from "@angular/common/http";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { DateValue } from "./components/datepicker/date-picker.component";
import { DateRange } from "./plot-view/plot-view.component";
import { DateTime } from "luxon";
@Injectable({
providedIn: 'root'
providedIn: "root",
})
export class PlotService {
readonly DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
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(
"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));
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);
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');
return this.http.get<Array<string>>(
"//" + window.location.hostname + ":" + window.location.port +
"/api/fields",
);
}
autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable<AutocompleteResult>
{
autocomplete(
query: string,
caretIndex: number,
resultMode: ResultMode,
): Observable<AutocompleteResult> {
const options = {
params: new HttpParams()
.set('caretIndex', ""+caretIndex)
.set('query', query)
.set('resultMode', resultMode)
.set("caretIndex", "" + caretIndex)
.set("query", query)
.set("resultMode", resultMode),
};
return this.http.get<AutocompleteResult>('//'+window.location.hostname+':'+window.location.port+'/api/autocomplete', options);
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)
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);
const result = this.http.post<PlotResponse>(
"//" + window.location.hostname + ":" + window.location.port +
"/api/plots",
plotRequest,
);
return result.pipe(map(this.enrichStats));
}
@@ -77,31 +241,48 @@ export class PlotService {
}
getFilterDefaults(): Observable<FilterDefaults> {
return this.http.get<FilterDefaults>('//'+window.location.hostname+':'+window.location.port+'/api/filters/defaults')
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)
)
(autocompleteResult: AutocompleteResult) =>
autocompleteResult.proposals.map((suggestion: Suggestion) =>
suggestion.value
),
),
);
}
toDateRange(dateValue: DateValue): Observable<DateRange> {
return this.http.post<{start: string, end:string, startEpochMilli: number, endEpochMilli: number}>("//" + window.location.hostname+":" + window.location.port +"/api/dates",dateValue)
.pipe(map((data) => {
const startDate = DateTime.fromFormat(data.start.slice(0, -1), this.DATE_PATTERN );
const endDate = DateTime.fromFormat(data.end.slice(0, -1), this.DATE_PATTERN );
return {
startDate: startDate,
endDate: endDate,
duration: endDate.diff(startDate),
};
}));
}
}
export class PlotType {
constructor(
public id: string,
public name: string,
public icon: string,
public active: boolean,
public xAxis: DataType,
public yAxis: DataType) {
public yAxis: DataType,
) {
}
compatible(others: Array<PlotType>): boolean {
@@ -135,7 +316,7 @@ export enum DataType {
Metric,
HistogramBin,
HistogramCount,
Other
Other,
}
export class AxesTypes {
@@ -181,10 +362,10 @@ export class AxesTypes {
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] : "");
return (x1 ? "x1:" + DataType[x1] : "") +
(y1 ? " y1:" + DataType[y1] : "") +
(x2 ? " x2:" + DataType[x2] : "") +
(y2 ? " y2:" + DataType[y2] : "");
}
}
@@ -192,10 +373,10 @@ export class Suggestion {
constructor(
public value: string,
public newQuery: string,
public newCaretPosition: number){}
public newCaretPosition: number,
) {}
}
export class AutocompleteResult {
constructor(public proposals: Array<Suggestion>) {}
}
@@ -212,35 +393,37 @@ export class PlotRequest {
constructor(
public submitterId: string,
public config: PlotConfig,
public renders: RenderOptionsMap
public renders: RenderOptionsMap,
) {}
copy(): PlotRequest {
return JSON.parse(JSON.stringify(this));
}
}
export class PlotConfig {
constructor( public query : string,
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 dateRange: DateValue,
public aggregates: Array<string>,
public intervalUnit: string,
public intervalValue: number,
public renderBarChartTickLabels: boolean = false,) {}
public renderBarChartTickLabels: boolean = false,
) {}
}
export class RenderOptions {
constructor(
public height: number,
public width: number,
public keyOutside: boolean,
public renderLabels: boolean) {}
public showKey: boolean,
public renderLabels: boolean,
) {}
}
export class YAxisDefinition {
@@ -248,13 +431,15 @@ export class YAxisDefinition {
public axisScale: string,
public rangeMin: number,
public rangeMax: number,
public rangeUnit : string){}
public rangeUnit: string,
) {}
}
export class PlotResponse {
constructor(
public stats: PlotResponseStats,
public rendered: RenderedImages){}
public rendered: RenderedImages,
) {}
}
export class PlotResponseStats {
@@ -264,7 +449,8 @@ export class PlotResponseStats {
public average: number,
public plottedValues: number,
public maxAvgRatio: number,
public dataSeriesStats : Array<DataSeriesStats>){}
public dataSeriesStats: Array<DataSeriesStats>,
) {}
}
export class DataSeriesStats {
@@ -275,23 +461,26 @@ export class DataSeriesStats {
public average: number,
public plottedValues: number,
public dashTypeAndColor: DashTypeAndColor,
public percentiles: Map<string, number>){}
public percentiles: Map<string, number>,
) {}
}
export class DashTypeAndColor {
constructor(
public color: string,
public pointType: number) {}
public pointType: number,
) {}
}
export class FilterDefaults {
constructor(
public groupBy: Array<string>,
public fields: Array<string>,
public splitBy: string){}
public splitBy: string,
) {}
}
export enum ResultMode {
CUT_AT_DOT = "CUT_AT_DOT",
FULL_VALUES = "FULL_VALUES"
FULL_VALUES = "FULL_VALUES",
}

View File

@@ -3,7 +3,8 @@ import { Component, OnInit } from '@angular/core';
@Component({
selector: 'pdb-upload-page',
templateUrl: './upload-page.component.html',
styleUrls: ['./upload-page.component.scss']
styleUrls: ['./upload-page.component.scss'],
standalone: true
})
export class UploadPageComponent implements OnInit {

View File

@@ -4,19 +4,31 @@
</div>
<div id="date-box">
<app-date-picker #datePicker></app-date-picker>
<!--
<mat-form-field class="pdb-form-full-width">
<mat-label>Date Range:</mat-label>
<input matInput id="search-date-range" value="dateRange" name="dates" />
</mat-form-field>
-->
</div>
<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)">
<mat-option *ngFor="let plotType of plotTypes" [value]="plotType" [disabled]="!plotType.active">
<img src="assets/img/{{plotType.icon}}.svg" class="icon-select" /> {{plotType.name}}
<mat-select
multiple
[(ngModel)]="selectedPlotType"
(ngModelChange)="changePlotType($event)"
>
<mat-option
*ngFor="let plotType of plotTypes"
[value]="plotType"
[disabled]="!plotType.active"
>
<img src="assets/img/{{ plotType.icon }}.svg" class="icon-select" />
{{ plotType.name }}
</mat-option>
</mat-select>
</mat-form-field>
@@ -24,7 +36,9 @@
<mat-form-field class="pdb-form-full-width">
<mat-label>Group By:</mat-label>
<mat-select multiple [(value)]="groupBy">
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{
tagField.name
}}</mat-option>
</mat-select>
</mat-form-field>
@@ -45,24 +59,39 @@
</mat-form-field>
</div>
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
<mat-checkbox [(ngModel)]="renderBarChartTickLabels">Show Tic Labels (bar chart)</mat-checkbox>
<mat-checkbox [(ngModel)]="renderBarChartTickLabels"
>Show Tic Labels (bar chart)</mat-checkbox
>
</div>
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
<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" (click)="toggleGallery($event)">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>
<mat-select [(value)]="splitBy">
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{
tagField.name
}}</mat-option>
</mat-select>
<mat-error *ngIf="splitBy == null || true">
Please select a value!
</mat-error>
</mat-form-field>
<div id="plot-button-bar">
<a
mat-icon-button
@@ -70,14 +99,22 @@
[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>
title="open new window with the same search"
><img src="assets/img/link.svg" aria-hidden="true"
/></a>
<button
*ngIf="!enableGallery && !plotJobActive"
[disabled]="plotJobActive"
mat-button
matTooltip="Create Plot"
(click)="plot()">
<img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" />
(click)="plot()"
>
<img
src="assets/img/scatter-chart2.svg"
class="icon-inline"
aria-hidden="true"
title="create plot"
/>
Plot
</button>
<button
@@ -85,15 +122,24 @@
mat-button
matTooltip="Create Gallery"
(click)="gallery()"
[disabled]="this.splitBy == null">
<img src="assets/img/four-squares-line.svg" class="icon-inline" aria-hidden="true" title="Create Gallery (only active if 'Split' is set)" />
[disabled]="this.splitBy == null"
>
<img
src="assets/img/four-squares-line.svg"
class="icon-inline"
aria-hidden="true"
title="Create Gallery (only active if 'Split' is set)"
/>
Gallery
</button>
<button
*ngIf="plotJobActive"
mat-button
(click)="abort()"
matTooltip="abort"><img src="assets/img/close.svg" class="icon-inline" /> Abort</button>
matTooltip="abort"
>
<img src="assets/img/close.svg" class="icon-inline" /> Abort
</button>
</div>
</div>
</div>
@@ -102,12 +148,8 @@
<pdb-plot-view
#plotView
(loadingEvent)="loading($event)"
(dateRangeUpdateEvent)="updateDateRange($event)"></pdb-plot-view>
<pdb-gallery-view
#galleryView>
</pdb-gallery-view>
(dateRangeUpdateEvent)="updateDateRange($event)"
></pdb-plot-view>
<pdb-gallery-view #galleryView> </pdb-gallery-view>
</div>
</div>

View File

@@ -15,7 +15,7 @@
grid:
"query-box query-box date-box" auto
"filters results results" 1fr
/ 25.5em 3fr 23.5em;
/ 25.5em 3fr auto;
}
}
@@ -43,6 +43,8 @@
#date-box{
grid-area: date-box;
display: flex;
align-items: center;
}

View File

@@ -1,21 +1,46 @@
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
import { PlotService, PlotType, PlotRequest, TagField, FilterDefaults, DataType, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap, Suggestion } from '../plot.service';
import { UntypedFormControl, } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { LimitByComponent } from '../limit-by/limit-by.component';
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
import { PlotViewComponent, LoadingEvent } from '../plot-view/plot-view.component';
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
import { WidgetDimensions } from '../dashboard.service';
import {
AfterViewInit,
Component,
Input,
OnInit,
ViewChild,
} from "@angular/core";
import {
AxesTypes,
DataType,
FilterDefaults,
PlotConfig,
PlotRequest,
PlotService,
PlotType,
RenderOptions,
RenderOptionsMap,
Suggestion,
TagField,
} from "../plot.service";
import { UntypedFormControl } from "@angular/forms";
import { MatSnackBar } from "@angular/material/snack-bar";
import { LimitByComponent } from "../limit-by/limit-by.component";
import { YAxisDefinitionComponent } from "../y-axis-definition/y-axis-definition.component";
import { QueryAutocompleteComponent } from "../query-autocomplete/query-autocomplete.component";
import {
DateRange,
LoadingEvent,
PlotViewComponent,
} from "../plot-view/plot-view.component";
import { GalleryViewComponent } from "../gallery-view/gallery-view.component";
import { WidgetDimensions } from "../dashboard.service";
import {
DatePickerComponent,
DateValue,
} from "../components/datepicker/date-picker.component";
@Component({
selector: 'pdb-visualization-page',
templateUrl: './visualization-page.component.html',
styleUrls: ['./visualization-page.component.scss']
selector: "pdb-visualization-page",
templateUrl: "./visualization-page.component.html",
styleUrls: ["./visualization-page.component.scss"],
})
export class VisualizationPageComponent implements OnInit, AfterViewInit {
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
@Input()
@@ -24,7 +49,9 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
@Input()
galleryEnabled = true;
dateRange = new UntypedFormControl('2019-10-05 00:00:00 - 2019-10-11 23:59:59');
dateRange = new UntypedFormControl(
"2019-10-05 00:00:00 - 2019-10-11 23:59:59",
);
selectedPlotType = new Array<PlotType>();
plotTypes: PlotType[] = [];
@@ -33,37 +60,38 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
groupBy = new Array<TagField>();
@ViewChild('limitbycomponent')
@ViewChild("limitbycomponent")
private limitbycomponent!: LimitByComponent;
@ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent })
@ViewChild("y1AxisDefinitionComponent", { read: YAxisDefinitionComponent })
private y1AxisDefinitionComponent!: YAxisDefinitionComponent;
@ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent })
@ViewChild("y2AxisDefinitionComponent", { read: YAxisDefinitionComponent })
private y2AxisDefinitionComponent!: YAxisDefinitionComponent;
@ViewChild('query')
@ViewChild("query")
query!: QueryAutocompleteComponent;
@ViewChild('plotView')
@ViewChild("plotView")
plotView!: PlotViewComponent;
@ViewChild('galleryView')
@ViewChild("galleryView")
galleryView!: GalleryViewComponent;
@ViewChild("datePicker")
datePicker!: DatePickerComponent;
enableGallery = false;
splitBy: TagField | undefined = undefined;
y2AxisAvailable = false;
intervalUnit = 'NO_INTERVAL';
intervalUnit = "NO_INTERVAL";
intervalValue = 1;
renderBarChartTickLabels = false;
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")!);
@@ -74,44 +102,53 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
showError(message: string) {
this.snackBar.open(message, "", {
duration: 5000,
verticalPosition: 'top'
verticalPosition: "top",
});
}
ngOnInit() {
(<any> window).initDatePicker();
this.plotTypes = this.plotService.getPlotTypes();
this.selectedPlotType.push(this.plotTypes[0]);
this.plotService.getFilterDefaults().subscribe((filterDefaults: FilterDefaults) => {
this.plotService.getFilterDefaults().subscribe(
(filterDefaults: FilterDefaults) => {
filterDefaults.fields.forEach((name: string) => {
this.tagFields.push(new TagField(name));
},
(error: any) => {
}, (error: any) => {
this.showError(error.error.message);
});
const groupByDefaults = this.defaultConfig ? this.defaultConfig.groupBy : filterDefaults.groupBy;
this.groupBy = this.tagFields.filter(val => groupByDefaults.includes(val.name));
this.splitBy = this.tagFields.find(val => filterDefaults.splitBy == val.name);
const groupByDefaults = this.defaultConfig
? this.defaultConfig.groupBy
: filterDefaults.groupBy;
this.groupBy = this.tagFields.filter((val) =>
groupByDefaults.includes(val.name)
);
this.splitBy = this.tagFields.find((val) =>
filterDefaults.splitBy == val.name
);
if (this.defaultConfig) {
this.plot();
}
});
},
);
}
ngAfterViewInit(): void {
if (this.defaultConfig) {
const c = this.defaultConfig;
this.query.suggestionFetcherEnabled = false;
this.query.queryField.setValue(new Suggestion(c.query, c.query, c.query.length));
this.query.queryField.setValue(
new Suggestion(c.query, c.query, c.query.length),
);
this.query.suggestionFetcherEnabled = true;
this.selectedPlotType = this.plotTypes.filter(pt => c.aggregates.includes(pt.id));
this.selectedPlotType = this.plotTypes.filter((pt) =>
c.aggregates.includes(pt.id)
);
this.changePlotType(this.selectedPlotType);
this.updateDateRange(c.dateRange, false);
this.limitbycomponent.limitBy = c.limitBy;
@@ -140,34 +177,36 @@ toggleGallery(event: Event){
this.plotJobActive = event.loading;
}
updateDateRange(newDateRange: string, updatePlot=true) {
(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
updateDateRange(newDateRange: DateValue, updatePlot = true) {
this.datePicker.setDateValue(newDateRange);
if (updatePlot) {
this.plot();
}
}
changePlotType(selectedPlotTypes: Array<PlotType>) {
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes));
this.plotTypes.forEach(pt => pt.active=false);
compatiblePlotTypes.forEach(pt => pt.active=true);
const compatiblePlotTypes = this.plotTypes.filter((pt) =>
pt.compatible(selectedPlotTypes)
);
this.plotTypes.forEach((pt) => pt.active = false);
compatiblePlotTypes.forEach((pt) => pt.active = true);
const axesTypes = this.getAxes();
this.y2AxisAvailable = axesTypes.y.length == 2;
}
selectedPlotTypesContains(plotTypeIds: Array<string>) {
return this.selectedPlotType.filter(pt => plotTypeIds.includes(pt.id)).length > 0;
return this.selectedPlotType.filter((pt) => plotTypeIds.includes(pt.id))
.length > 0;
}
dateRangeAsString() : string {
return (<HTMLInputElement>document.getElementById("search-date-range")).value;
dateRangeAsString(): DateValue {
return this.datePicker.getDateValue();
}
gallery() {
if (this.splitBy != null) {
this.plotView.imageUrl = '';
this.plotView.imageUrl = "";
this.plotView.stats = null;
this.galleryView.show = true;
const request = this.createPlotRequest();
@@ -178,7 +217,6 @@ toggleGallery(event: Event){
}
getAxes(): AxesTypes {
const x = new Array<DataType>();
const y = new Array<DataType>();
@@ -198,7 +236,7 @@ toggleGallery(event: Event){
abort() {
this.plotService.abort((<any> window).submitterId).subscribe({
complete: () => {
}
},
});
}
@@ -211,24 +249,27 @@ toggleGallery(event: Event){
const results = document.getElementById("results");
return new WidgetDimensions(
results != null ? results.offsetWidth - 1 : 1024,
results != null ? results.offsetHeight-1: 1024);
results != null ? results.offsetHeight - 1 : 1024,
);
}
createPlotConfig(): PlotConfig {
const aggregates = new Array<string>();
this.selectedPlotType.forEach(a => aggregates.push(a.id));
this.selectedPlotType.forEach((a) => aggregates.push(a.id));
const y1 = this.y1AxisDefinitionComponent.getAxisDefinition();
const y2 = this.y2AxisDefinitionComponent ? this.y2AxisDefinitionComponent.getAxisDefinition() : undefined;
const y2 = this.y2AxisDefinitionComponent
? this.y2AxisDefinitionComponent.getAxisDefinition()
: undefined;
const config = new PlotConfig(
this.query.query,
this.groupBy.map(o => o.name),
this.groupBy.map((o) => o.name),
this.limitbycomponent.limitBy,
this.limitbycomponent.limit,
y1,
y2,
this.dateRangeAsString(), // dateRange
this.datePicker.getDateValue(), // dateRange
aggregates, // aggregates
this.intervalUnit,
this.intervalValue,
@@ -243,14 +284,19 @@ toggleGallery(event: Event){
const config = this.createPlotConfig();
const renderOptions: RenderOptionsMap = {
'main': new RenderOptions(results!.offsetHeight-1, results!.offsetWidth-1, false, true),
'thumbnail': new RenderOptions(200, 300, false, false),
"main": new RenderOptions(
results!.offsetHeight - 1,
results!.offsetWidth - 1,
true,
true,
),
"thumbnail": new RenderOptions(200, 300, false, false),
};
const request = new PlotRequest(
(<any> window).submitterId,
config,
renderOptions
renderOptions,
);
return request;
}

View File

@@ -1,10 +1,27 @@
import { Component, Input } from '@angular/core';
import { YAxisDefinition } from '../plot.service';
import { BrowserModule } from '@angular/platform-browser';
import { MatFormField, MatLabel } from '@angular/material/form-field';
import { MatOptgroup, MatOption, MatSelect } from '@angular/material/select';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatInput } from '@angular/material/input';
@Component({
selector: 'pdb-y-axis-definition',
templateUrl: './y-axis-definition.component.html',
styleUrls: ['./y-axis-definition.component.scss']
styleUrls: ['./y-axis-definition.component.scss'],
standalone: true,
imports: [
BrowserModule,
FormsModule,
MatFormField,
MatInput,
MatLabel,
MatSelect,
MatOption,
MatOptgroup,
ReactiveFormsModule
]
})
export class YAxisDefinitionComponent {

View File

@@ -47,6 +47,22 @@
});
}
function initSimpleDatePicker() {
$('input[name="dates"]').daterangepicker({
timePicker: true,
minDate: "2017-01-01",
maxDate: "2029-12-31",
maxYear: parseInt(moment().format('YYYY'),10),
timePicker24Hour: true,
timePickerSeconds: true,
showDropdowns: true, // drop downs for selecting year and month
locale: {
format: 'YYYY-MM-DD HH:mm:ss',
"firstDay": 1 // monday is the first day of the week
},
});
}
$( document ).ready(function() {
initInvaders('results');
document.addEventListener("invadersPause", function(event) {

View File

@@ -14,25 +14,21 @@
// If you don't need the default component typographies but still want the hierarchy styles,
// you can delete this line and instead use:
// `@include mat.legacy-typography-hierarchy(mat.define-typography-config());`
/* TODO(mdc-migration): Remove all-legacy-component-typographies once all legacy components are migrated*/
@include mat.all-legacy-component-typographies();
@include mat.all-component-typographies();
/* TODO(mdc-migration): Remove legacy-core once all legacy components are migrated*/
@include mat.legacy-core();
@include mat.core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$candy-app-primary: mat.define-palette(mat.$blue-palette);
$candy-app-accent: mat.define-palette(mat.$blue-palette, A200, A100, A400);
$candy-app-primary: mat.m2-define-palette(mat.$m2-blue-palette);
$candy-app-accent: mat.m2-define-palette(mat.$m2-blue-palette, A200, A100, A400);
// The warn palette is optional (defaults to red).
$candy-app-warn: mat.define-palette(mat.$red-palette);
$candy-app-warn: mat.m2-define-palette(mat.$m2-red-palette);
// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$candy-app-theme: mat.define-light-theme((
$candy-app-theme: mat.m2-define-light-theme((
color: (
primary: $candy-app-primary,
accent: $candy-app-accent,
@@ -45,8 +41,6 @@ $candy-app-theme: mat.define-light-theme((
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
/* TODO(mdc-migration): Remove all-legacy-component-themes once all legacy components are migrated*/
@include mat.all-legacy-component-themes($candy-app-theme);
@include mat.all-component-themes($candy-app-theme);
@@ -97,6 +91,10 @@ h2 {
margin-block-end: 0.83rem;
}
.hidden {
visibility: hidden;
}
.icon-inline {
width: 1em;
height: 1em;
@@ -178,6 +176,9 @@ a.external-link:after {
mat-form-field.pdb-form-full-width {
width: 100%;
}
mat-form-field.pdb-form-number-small {
width: 4.5em;
}
mat-form-field.pdb-form-number {
width: 5.5em;
}
@@ -197,7 +198,7 @@ app-add-text-dialog .mat-mdc-form-field-subscript-wrapper {
.errorPanel {
padding: 1ex;
background-color: map-get(mat.$red-palette, 100);
background-color: map-get(mat.$m2-red-palette, 100);
border-radius: 5px;
}
@@ -247,3 +248,34 @@ markdown tfoot {
markdown pre {
font-family: monospace;
}
.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

@@ -5,6 +5,7 @@
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
@@ -12,7 +13,6 @@
"noFallthroughCasesInSwitch": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,

View File

@@ -0,0 +1,143 @@
package org.lucares.pdb.plot.api;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.lucares.pdb.api.DateTimeRange;
import org.lucares.utils.Preconditions;
public class DateTimeRangeParser {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static DateTimeRange parse(final OffsetDateTime offsetTime, final String datePeriod) {
final String[] startEnd = datePeriod.split(Pattern.quote("/"));
final String start = startEnd[0];
final String end = startEnd[1];
final OffsetDateTime startTime = parseInternal(offsetTime, start);
final OffsetDateTime endTime = parseInternal(offsetTime, end);
return new DateTimeRange(startTime, endTime);
}
private static OffsetDateTime parseInternal(final OffsetDateTime offsetTime, final String timeDefinition) {
final Pattern regex = Pattern.compile("(?<beginEnd>[BE])(?<amountUnit>(\\-?[0-9]*[mHDWMY])+)",
Pattern.MULTILINE);
final Matcher matcher = regex.matcher(timeDefinition);
if (matcher.matches()) {
final String beginEnd = matcher.group("beginEnd");
final boolean begin = "B".equals(beginEnd);
OffsetDateTime result = offsetTime;
final String amountUnitString = matcher.group("amountUnit");
final Pattern regexAmountUnit = Pattern.compile("(?<amount>\\-?[0-9]*)(?<unit>[mHDWMY])");
final Matcher m = regexAmountUnit.matcher(amountUnitString);
while (m.find()) {
final String amountString = m.group("amount");
final String unitString = m.group("unit");
final int amount = amountString.equals("") ? 0 : Integer.parseInt(amountString);
switch (unitString) {
case "m": {
final ChronoUnit unit = ChronoUnit.MINUTES;
if (begin) {
result = result.plus(amount, unit).truncatedTo(unit);
} else {
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
}
break;
}
case "H": {
final ChronoUnit unit = ChronoUnit.HOURS;
if (begin) {
result = result.plus(amount, unit).truncatedTo(unit);
} else {
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
}
break;
}
case "D": {
final ChronoUnit unit = ChronoUnit.DAYS;
if (begin) {
result = result.plus(amount, unit).truncatedTo(unit);
} else {
result = result.plus(amount + 1, unit).truncatedTo(unit).minusSeconds(1);
}
break;
}
case "W": {
final DayOfWeek firstDayOfWeek = DayOfWeek.MONDAY;
final DayOfWeek lastDayOfWeek = DayOfWeek
.of(((firstDayOfWeek.getValue() - 1 + 6) % DayOfWeek.values().length) + 1); // weird
// computation,
// because
// DayOfWeek
// goes from 1
// to 7
final ChronoUnit unit = ChronoUnit.WEEKS;
if (begin) {
result = result.plus(amount, unit).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))
.truncatedTo(ChronoUnit.DAYS);
} else {
result = result.plus(amount, unit).with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
.plus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.DAYS).minusSeconds(1);
}
break;
}
case "M": {
final ChronoUnit unit = ChronoUnit.MONTHS;
if (begin) {
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
} else {
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1)
.plus(1, ChronoUnit.MONTHS).minusSeconds(1);
}
break;
}
case "Y": {
final ChronoUnit unit = ChronoUnit.YEARS;
if (begin) {
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
} else {
result = result.plus(amount, unit).truncatedTo(ChronoUnit.DAYS).withDayOfYear(1)
.plus(1, ChronoUnit.YEARS).minusSeconds(1);
}
break;
}
default:
throw new IllegalArgumentException("Unexpected value: " + unitString);
}
}
return result;
}
throw new IllegalArgumentException("invalid input: " + timeDefinition);
}
public static DateTimeRange parseAbsolute(final String dateRangeAsString) {
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
final String startString = startEnd[0];
final String endString = startEnd[1];
final OffsetDateTime start = LocalDateTime.parse(startString, DATE_FORMAT).atOffset(ZoneOffset.UTC);
final OffsetDateTime end = LocalDateTime.parse(endString, DATE_FORMAT).atOffset(ZoneOffset.UTC);
return new DateTimeRange(start, end);
}
}

View File

@@ -0,0 +1,38 @@
package org.lucares.pdb.plot.api;
public class DateValue {
public enum DateType {
QUICK, RELATIVE, ABSOLUTE
}
private DateType type;
private String display;
private String value;
public DateType getType() {
return type;
}
public void setType(final DateType type) {
this.type = type;
}
public String getDisplay() {
return display;
}
public void setDisplay(final String display) {
this.display = display;
}
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}

View File

@@ -1,23 +1,19 @@
package org.lucares.pdb.plot.api;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.lucares.pdb.api.DateTimeRange;
import org.lucares.recommind.logs.GnuplotAxis;
import org.lucares.recommind.logs.GnuplotSettings;
import org.lucares.utils.Preconditions;
public class PlotSettings {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String query;
@@ -27,7 +23,7 @@ public class PlotSettings {
private int limit;
private String dateRangeAsString;
private DateValue dateValue;
private YAxisDefinition y1;
private YAxisDefinition y2;
@@ -80,30 +76,32 @@ public class PlotSettings {
this.limit = limit;
}
public String getDateRange() {
return dateRangeAsString;
public DateValue getDateRange() {
return dateValue;
}
public void setDateRange(final String dateRangeAsString) {
this.dateRangeAsString = dateRangeAsString;
public void setDateRange(final DateValue dateValue) {
this.dateValue = dateValue;
}
public DateTimeRange dateRange() {
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
return new DateTimeRange(startDate, endDate);
switch (this.dateValue.getType()) {
case RELATIVE:
case QUICK:
final DateTimeRange dateTimeRange = DateTimeRangeParser.parse(OffsetDateTime.now(), dateValue.getValue());
return dateTimeRange;
case ABSOLUTE:
return DateTimeRangeParser.parseAbsolute(dateValue.getValue());
}
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return "PlotSettings [query=" + query + ", groupBy=" + groupBy + ", limitBy=" + limitBy + ", limit=" + limit
+ ", dateRangeAsString=" + dateRangeAsString + ", y1=" + y1 + " y2=" + y2 + ", aggregates=" + aggregates
+ ", dateRangeAsString=" + dateValue + ", y1=" + y1 + " y2=" + y2 + ", aggregates=" + aggregates
+ ", renders=" + renders + "]";
}

View File

@@ -3,7 +3,7 @@ package org.lucares.pdb.plot.api;
public class RenderOptions {
private int height;
private int width;
private boolean keyOutside;
private boolean showKey;
private boolean renderLabels;
public int getHeight() {
@@ -22,12 +22,12 @@ public class RenderOptions {
this.width = width;
}
public boolean isKeyOutside() {
return keyOutside;
public boolean isShowKey() {
return showKey;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
public void setShowKey(final boolean showKey) {
this.showKey = showKey;
}
public boolean isRenderLabels() {

View File

@@ -50,10 +50,9 @@ public class GnuplotFileGenerator implements Appender {
appendln(result, "set nokey");
} else {
if (settings.isKeyOutside()) {
appendfln(result, "set key outside");
} else {
if (!settings.isShowKey()) {
appendfln(result, "set nokey");
}
// make sure left and right margins are always the same
// this is need to be able to zoom in by selecting a region
// (horizontal: 1 unit = 10px; vertical: 1 unit = 19px)
@@ -62,7 +61,6 @@ public class GnuplotFileGenerator implements Appender {
appendln(result, "set tmargin 3"); // margin 3 -> 57px - marker (1)
appendln(result, "set bmargin 4"); // margin 4 -> 76
}
}
// appendfln(result, "set xrange [-1:1]");
appendfln(result, "set boxwidth 0.5");

View File

@@ -30,7 +30,7 @@ public class GnuplotSettings {
private YAxisDefinition y1;
private YAxisDefinition y2;
private AggregateHandlerCollection aggregates;
private boolean keyOutside = false;
private boolean showKey = false;
private AxisSettings xAxisSettings = new AxisSettings();
private boolean renderLabels = true;
@@ -101,12 +101,12 @@ public class GnuplotSettings {
return aggregates;
}
public void setKeyOutside(final boolean keyOutside) {
this.keyOutside = keyOutside;
public void setShowKey(final boolean showKey) {
this.showKey = showKey;
}
public boolean isKeyOutside() {
return keyOutside;
public boolean isShowKey() {
return showKey;
}
public void renderLabels(final boolean renderLabels) {

View File

@@ -126,7 +126,7 @@ public class Plotter {
gnuplotSettings.setY1(plotSettings.getY1());
gnuplotSettings.setY2(plotSettings.getY2());
gnuplotSettings.setAggregates(plotSettings.getAggregates());
gnuplotSettings.setKeyOutside(renderOptions.isKeyOutside());
gnuplotSettings.setShowKey(renderOptions.isShowKey());
gnuplotSettings.renderLabels(renderOptions.isRenderLabels());
gnuplot.plot(gnuplotSettings, dataSeries);
}

View File

@@ -60,6 +60,7 @@ class YAxisTicks {
} else {
switch (yAxisDefinition.getAxisScale()) {
case LINEAR:
result = computeLinearYTicksNoUnit(height, yRangeMin, yRangeMax);
break;
case LOG10:
result = computeLog10YTicksNoUnit(height, yRangeMin, yRangeMax);
@@ -206,25 +207,103 @@ class YAxisTicks {
final List<String> ticsLabels = Arrays.asList(//
"\"1\" 1", //
"\"2\" 2", //
"\"5\" 5", //
"\"10\" 10", //
"\"20\" 20", //
"\"50\" 50", //
"\"100\" 100", //
"\"200\" 200", //
"\"500\" 500", //
"\"1000\" 1000", //
"\"2000\" 2000", //
"\"5000\" 5000", //
"\"10k\" 10000", //
"\"20k\" 20000", //
"\"50k\" 50000", //
"\"100k\" 100000", //
"\"200k\" 200000", //
"\"500k\" 500000", //
"\"1m\" 1000000", //
"\"2m\" 2000000", //
"\"5m\" 5000000", //
"\"10m\" 10000000", //
"\"20m\" 20000000", //
"\"50m\" 50000000", //
"\"100m\" 100000000", //
"\"200m\" 200000000", //
"\"500m\" 500000000", //
"\"1b\" 1000000000.0", //
"\"2b\" 2000000000.0", //
"\"5b\" 5000000000.0", //
"\"10b\" 10000000000.0", //
"\"20b\" 20000000000.0", //
"\"50b\" 50000000000.0", //
"\"100b\" 100000000000.0", //
"\"200b\" 200000000000.0", //
"\"500b\" 500000000000.0", //
"\"1t\" 1000000000000.0", //
"\"2t\" 2000000000000.0", //
"\"5t\" 5000000000000.0", //
"\"10t\" 10000000000000.0", //
"\"100t\" 100000000000000.0" //
"\"20t\" 20000000000000.0", //
"\"50t\" 50000000000000.0", //
"\"100t\" 100000000000000.0", //
"\"200t\" 2000000000000000.0", //
"\"500t\" 5000000000000000.0", //
"\"1q\" 10000000000000000.0", //
"\"2q\" 20000000000000000.0", //
"\"5q\" 50000000000000000.0" //
);
return ticsLabels;
}
private static List<String> computeLinearYTicksNoUnit(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 = roundToNextLinearNoUnitStep(range / maxLabels);
final List<String> ticsLabels = new ArrayList<>();
for (long i = yRangeMinInMs; i <= yRangeMaxInMs; i += rangePerLabel) {
ticsLabels.add("\"" + valueToTic(i, rangePerLabel) + "\" " + i);
}
return ticsLabels;
}
private static String valueToTic(final long val, final double rangePerLabel) {
final List<String> powers = List.of("", "k", "m", "b", "t", "q");
int power = 1;
String result = String.format("%d", val);
while (val >= Math.pow(1000, power) && power < powers.size()) {
result = String.format("%.3f", val / Math.pow(1000, power));
result = result.replaceAll("\\.?0*$", "");
result = result + powers.get(power);
power = power + 1;
}
return result;
}
private static long roundToNextLinearNoUnitStep(final long stepSize) {
final List<Long> steps = Arrays.asList(1L, 5L, 10L, 20L, 25L, 50L, 100L, 200L, 250L, 500L, 1000L, 2000L, 2500L,
5000L, 10_000L, 20_000L, 25_000L, 50_000L, 100_000L, 200_000L, 250_000L, 500_000L, 1_000_000L,
2_000_000L, 2_500_000L, 5_000_000L, 10_000_000L, 20_000_000L, 25_000_000L, 100_000_000L, 200_000_000L,
250_000_000L, 500_000_000L, 1_000_000_000L, 2_000_000_000L, 2_500_000_000L, 5_000_000_000L);
for (final Long step : steps) {
if (stepSize < step) {
return step;
}
}
return stepSize;
}
private static long roundToLinearLabelSteps(final long msPerLabel) {
final List<Long> steps = Arrays.asList(2L, 5L, 10L, 20L, 50L, 100L, 200L, 500L, 1000L, 2000L, 5000L, 10_000L,
20_000L, MINUTES.toMillis(1), MINUTES.toMillis(2), MINUTES.toMillis(5), MINUTES.toMillis(10),

View File

@@ -0,0 +1,133 @@
package org.lucares.pdb.plot.api;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lucares.pdb.api.DateTimeRange;
public class DateTimeRangeParserTest {
@Test
public void test() {
}
public static Stream<Arguments> providerDatePeriods() {
return Stream.of(//
// last 15 minutes
Arguments.of("2000-01-02 12:59:59", "B-14m/Em", "2000-01-02 12:45:00", "2000-01-02 12:59:59"),
// this hour
Arguments.of("2000-01-02 12:00:00", "BH/EH", "2000-01-02 12:00:00", "2000-01-02 12:59:59"),
Arguments.of("2000-01-02 12:59:59", "BH/EH", "2000-01-02 12:00:00", "2000-01-02 12:59:59"),
// previous hour
Arguments.of("2000-01-02 12:00:00", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"),
Arguments.of("2000-01-02 12:59:58", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"),
Arguments.of("2000-01-02 12:59:59", "B-1H/E-1H", "2000-01-02 11:00:00", "2000-01-02 11:59:59"),
// today
Arguments.of("2000-01-02 12:00:00", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"),
Arguments.of("2000-01-02 00:00:00", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"),
Arguments.of("2000-01-02 23:59:59", "BD/ED", "2000-01-02 00:00:00", "2000-01-02 23:59:59"),
Arguments.of("2000-01-02 12:00:00", "B0D/E0D", "2000-01-02 00:00:00", "2000-01-02 23:59:59"),
// tomorrow
Arguments.of("2000-01-02 12:00:00", "B1D/E1D", "2000-01-03 00:00:00", "2000-01-03 23:59:59"),
// yesterday
Arguments.of("2000-01-02 12:00:00", "B-1D/E-1D", "2000-01-01 00:00:00", "2000-01-01 23:59:59"),
// this week
Arguments.of("2024-04-22 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-23 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-24 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-25 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-26 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-27 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
Arguments.of("2024-04-28 12:00:00", "BW/EW", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
// previous week
Arguments.of("2024-04-29 12:00:00", "B-1W/E-1W", "2024-04-22 00:00:00", "2024-04-28 23:59:59"),
// last 4 week (including this one
Arguments.of("2024-04-28 12:00:00", "B-3W/EW", "2024-04-01 00:00:00", "2024-04-28 23:59:59"),
// this month
Arguments.of("2024-04-29 12:00:00", "BM/EM", "2024-04-01 00:00:00", "2024-04-30 23:59:59"),
// previous month (in a leap year)
Arguments.of("2023-03-29 12:00:00", "B-1M/E-1M", "2023-02-01 00:00:00", "2023-02-28 23:59:59"),
// previous month (in a leap year)
Arguments.of("2024-03-29 12:00:00", "B-1M/E-1M", "2024-02-01 00:00:00", "2024-02-29 23:59:59"),
// last 3 months
Arguments.of("2024-03-29 12:00:00", "B-2M/EM", "2024-01-01 00:00:00", "2024-03-31 23:59:59"),
// previous 3 months (in a leap year)
Arguments.of("2024-03-29 12:00:00", "B-3M/E-1M", "2023-12-01 00:00:00", "2024-02-29 23:59:59"),
// this year
Arguments.of("2024-03-29 12:00:00", "BY/EY", "2024-01-01 00:00:00", "2024-12-31 23:59:59"),
// previous year
Arguments.of("2024-03-29 12:00:00", "B-1Y/E-1Y", "2023-01-01 00:00:00", "2023-12-31 23:59:59"),
//
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", "2024-03-29 12:00:00")
);
}
@ParameterizedTest
@MethodSource("providerDatePeriods")
public void testDatePeriods(final String now, final String datePeriod, final String expectedStart,
final String expectedEnd) throws Exception {
final OffsetDateTime offsetTime = LocalDateTime.parse(now, PlotSettings.DATE_FORMAT).atOffset(ZoneOffset.UTC);
final DateTimeRange actual = DateTimeRangeParser.parse(offsetTime, datePeriod);
final String actualStart = PlotSettings.DATE_FORMAT.format(actual.getStart());
final String actualEnd = PlotSettings.DATE_FORMAT.format(actual.getEnd());
System.out.println("at " + now + " " + datePeriod + " -> " + actualStart + " - " + actualEnd);
Assertions.assertEquals(expectedStart, actualStart, "start");
Assertions.assertEquals(expectedEnd, actualEnd, "end");
}
public static Stream<Arguments> providerDatePeriods_multiple() {
return Stream.of(//
//
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/Bm", "2024-03-29 10:45:00", "2024-03-29 12:00:00"),
Arguments.of("2024-03-29 12:00:00", "B-1H-15m/EH15m", "2024-03-29 10:45:00", "2024-03-29 13:14:59")
);
}
@ParameterizedTest
@MethodSource("providerDatePeriods_multiple")
public void testDatePeriods_multiple(final String now, final String datePeriod, final String expectedStart,
final String expectedEnd) throws Exception {
final OffsetDateTime offsetTime = LocalDateTime.parse(now, PlotSettings.DATE_FORMAT).atOffset(ZoneOffset.UTC);
final DateTimeRange actual = DateTimeRangeParser.parse(offsetTime, datePeriod);
final String actualStart = PlotSettings.DATE_FORMAT.format(actual.getStart());
final String actualEnd = PlotSettings.DATE_FORMAT.format(actual.getEnd());
System.out.println("at " + now + " " + datePeriod + " -> " + actualStart + " - " + actualEnd);
Assertions.assertEquals(expectedStart, actualStart, "start");
Assertions.assertEquals(expectedEnd, actualEnd, "end");
}
}

View File

@@ -3,6 +3,8 @@ package org.lucares.pdbui;
import java.io.IOException;
import java.nio.file.Path;
import java.text.Collator;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -26,6 +28,9 @@ import org.lucares.pdb.api.DateTimeRange;
import org.lucares.pdb.api.QueryWithCaretMarker;
import org.lucares.pdb.api.QueryWithCaretMarker.ResultMode;
import org.lucares.pdb.datastore.Proposal;
import org.lucares.pdb.plot.api.DateTimeRangeParser;
import org.lucares.pdb.plot.api.DateValue;
import org.lucares.pdb.plot.api.DateValue.DateType;
import org.lucares.pdb.plot.api.PlotSettings;
import org.lucares.pdbui.domain.AutocompleteProposal;
import org.lucares.pdbui.domain.AutocompleteProposalByValue;
@@ -345,6 +350,22 @@ public class PdbController implements HardcodedValues, PropertyKeys {
return result;
}
@RequestMapping(path = "/dates", //
method = RequestMethod.POST, //
consumes = MediaType.APPLICATION_JSON_VALUE, //
produces = MediaType.APPLICATION_JSON_VALUE //
)
@ResponseBody
public DateTimeRange dates(@RequestBody final DateValue dateValue) {
final DateType type = dateValue.getType();
final DateTimeRange result = switch (type) {
case RELATIVE -> DateTimeRangeParser.parse(OffsetDateTime.now(ZoneId.of("UTC")), dateValue.getValue());
case QUICK -> DateTimeRangeParser.parse(OffsetDateTime.now(ZoneId.of("UTC")), dateValue.getValue());
case ABSOLUTE -> DateTimeRangeParser.parseAbsolute(dateValue.getValue());
};
return result;
}
@PostMapping(path = "/data", consumes = MediaType.MULTIPART_MIXED_VALUE)
@ResponseBody
@ResponseStatus(code = HttpStatus.CREATED)

View File

@@ -26,7 +26,7 @@ public class WebConfiguration implements WebMvcConfigurer, HardcodedValues, Prop
addResourceHandlerForPlottedImages(registry);
// addResourceHandlerForAngular(registry);
addResourceHandlerForAngular(registry);
}
private void addResourceHandlerForPlottedImages(final ResourceHandlerRegistry registry) {
@@ -57,7 +57,7 @@ public class WebConfiguration implements WebMvcConfigurer, HardcodedValues, Prop
// to determine which sub-page to show.
//
// This makes Angular also responsible for all 404 pages.
registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/").resourceChain(true)
registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/browser/").resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(final String resourcePath, final Resource location)
@@ -65,7 +65,7 @@ public class WebConfiguration implements WebMvcConfigurer, HardcodedValues, Prop
final Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/resources/index.html");
: new ClassPathResource("/resources/browser/index.html");
}
});
}

View File

@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.lucares.pdb.plot.api.Aggregate;
import org.lucares.pdb.plot.api.DateValue;
import org.lucares.pdb.plot.api.Limit;
import org.lucares.pdb.plot.api.YAxisDefinition;
@@ -25,7 +26,7 @@ public class PlotConfig {
private YAxisDefinition y1 = new YAxisDefinition();
private YAxisDefinition y2 = new YAxisDefinition();
private String dateRange;
private DateValue dateRange;
private List<Aggregate> aggregates = new ArrayList<>();
@@ -66,11 +67,11 @@ public class PlotConfig {
this.limit = limit;
}
public String getDateRange() {
public DateValue getDateRange() {
return dateRange;
}
public void setDateRange(final String dateRange) {
public void setDateRange(final DateValue dateRange) {
this.dateRange = dateRange;
}

View File

@@ -1,7 +1,7 @@
db.base=D:/ws/pdb/databases/test
db.base=c:/ws/pdb/databases/test
server.port=17333
gnuplot.home=D:/ws/pdb/gnuplot-5.2
cache.images.duration.seconds=86400
defaults.groupBy=pod,method,metric
defaults.splitBy=method
defaults.query.examples=pod=vapfinra01 and method=ViewService.findFieldView,ViewService.findFieldViewGroup;pod=vappilby01 and method=ReviewInContextController.index;pod=vapnyse001 and method=ReviewInContextController.index,ReviewController.index
defaults.query.examples=pod=vadperfo01 and method=ViewService.findFieldView,ViewService.findFieldViewGroup;pod=vadperfo01 and method=ReviewInContextController.index;pod=vadperfo01 and method=ReviewInContextController.index,ReviewController.index

View File

@@ -28,15 +28,10 @@ public class VariableByteEncoder {
public static final long MIN_VALUE = Long.MIN_VALUE / 2 + 1;
public static final long MAX_VALUE = Long.MAX_VALUE / 2;
private static final int MAX_BYTES_PER_VALUE = 10;
private static final int CONTINUATION_BYTE_FLAG = 1 << 7; // 10000000
private static final long DATA_BITS = (1 << 7) - 1; // 01111111
private static final ThreadLocal<byte[]> SINGLE_VALUE_BUFFER = ThreadLocal
.withInitial(() -> new byte[MAX_BYTES_PER_VALUE]);
/**
* Encodes time and value into the given buffer.
* <p>
@@ -137,8 +132,18 @@ public class VariableByteEncoder {
* input: 0 1 -1 2 -2 3 -3
* encoded: 1 2 3 4 5 6 7
* </pre>
*
* Note: 1. I tried to replace this ternary operator with a branchfree
* alternative, but it was slower:
*
* <pre>
* final long sign = (x - 1) >> 63; // will be 111...1 if first bit was 1 (x was negative) and 000..0
* // otherwise
* final long signBit = sign & 0x1; // will be 1 for negative x and 0 otherwise
* return (((x * 2) ^ (sign))) + (signBit * 2); // same as above, but branchless
* </pre>
*/
private static long encodeIntoPositiveValue(final long value) {
static long encodeIntoPositiveValue(final long value) {
return value > 0 ? value * 2 : (value * -2) + 1;
}
@@ -234,9 +239,9 @@ public class VariableByteEncoder {
}
public static int neededBytes(final long value) {
final byte[] buffer = SINGLE_VALUE_BUFFER.get();
final int usedBytes = encodeInto(value, buffer, 0);
return usedBytes;
final long val = encodeIntoPositiveValue(value);
final int numberOfOnes = 64 - Long.numberOfLeadingZeros(val);
return numberOfOnes / 7 + (numberOfOnes % 7 == 0 ? 0 : 1);
}
}

View File

@@ -81,7 +81,7 @@ public class VariableByteEncoderTest {
Assertions.assertEquals(originalValues, decodedValues);
}
public static Stream<Arguments> providerNededBytes() {
public static Stream<Arguments> providerNeededBytes() {
return Stream.of( //
Arguments.of(0, 1), //
Arguments.of(-10, 1), //
@@ -98,7 +98,7 @@ public class VariableByteEncoderTest {
}
@ParameterizedTest
@MethodSource("providerNededBytes")
@MethodSource("providerNeededBytes")
public void testNeededBytes(final long value, final int expectedNeededBytes) {
final int neededBytes = VariableByteEncoder.neededBytes(value);

View File

@@ -1,7 +1,8 @@
// include all projects with a build.gradle
// (this does not support nested projects)
File srcDir = new File(".")
FileCollection collection = files { srcDir.listFiles() }
collection.filter{ new File(it, "build.gradle").isFile() }.each{ include it.getName() }
include("block-storage")
include("data-store")
include("pdb-api")
include("pdb-js")
include("pdb-plotting")
include("pdb-ui")
include("pdb-utils")
include("performanceDb")