Compare commits
63 Commits
f8a199fd6a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fa85dacb4 | |||
| 64725de8b9 | |||
| 768962aa43 | |||
| 1fd7becf9a | |||
| e292587a36 | |||
| 8fef666183 | |||
| 08a481b5ba | |||
| 526e1d842e | |||
| 06c9b4998f | |||
| ac5dcdc58f | |||
| 296d42e721 | |||
| 9db020ceb0 | |||
| 36da503be9 | |||
| 42751f84d4 | |||
| 39d7c029ea | |||
| f072185074 | |||
| 122ba11a79 | |||
| f1d7799bf1 | |||
| 680f1bff03 | |||
| fa0315650a | |||
| 8f765dd478 | |||
| 1234560512 | |||
| 2711579afb | |||
| 3ac021e45f | |||
| c199eae4ff | |||
| 6073dd0779 | |||
| fee5eda780 | |||
| cc0db6d732 | |||
| f084396e95 | |||
| e4b6eea4b1 | |||
| 75fa966af3 | |||
| a3e4917e89 | |||
| 05fc03e48f | |||
| b00ce507ef | |||
| a69fe09464 | |||
| 77b99801e4 | |||
| da210145e6 | |||
| 21a84b5223 | |||
| a99a884423 | |||
| 6d6b6ba00c | |||
| 380bad6967 | |||
| f3556b6909 | |||
| 452030de5e | |||
| 6b8e3d2089 | |||
| b0467c4571 | |||
| a64e851c33 | |||
| fefc7411c1 | |||
| 00a1bf8ffb | |||
| 317d31bbda | |||
| d339f5307f | |||
| b3085c9b0c | |||
| 96955e0515 | |||
| 9a41f132f8 | |||
| 33c4fe1448 | |||
| d320ff3d93 | |||
| 4ce9cca7e0 | |||
| 29215f0410 | |||
| abc60c6de2 | |||
| c969c5c848 | |||
| 6552d51bcc | |||
| 43e13b53b1 | |||
| 731e9264e3 | |||
| 8839ab52a2 |
31
build.gradle
31
build.gradle
@@ -4,27 +4,27 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'eclipse'
|
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 {
|
ext {
|
||||||
|
|
||||||
javaVersion=17
|
javaVersion=21
|
||||||
|
|
||||||
version_log4j2= '2.19.0' // keep in sync with spring-boot-starter-log4j2
|
version_log4j2= '2.20.0' // keep in sync with spring-boot-starter-log4j2
|
||||||
version_spring = '3.0.4'
|
version_spring = '3.3.4'
|
||||||
version_junit = '5.9.2'
|
version_junit = '5.11.1'
|
||||||
version_junit_platform = '1.9.2'
|
version_junit_platform = '1.11.1'
|
||||||
version_nodejs = '16.17.1' // keep in sync with npm
|
version_nodejs = '20.17.0' // keep in sync with npm
|
||||||
version_npm = '8.15.0' // keep in sync with nodejs
|
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_collections4 = 'org.apache.commons:commons-collections4:4.4'
|
||||||
lib_commons_csv= 'org.apache.commons:commons-csv:1.10.0'
|
lib_commons_csv= 'org.apache.commons:commons-csv:1.12.0'
|
||||||
lib_commons_lang3 = 'org.apache.commons:commons-lang3:3.12.0'
|
lib_commons_lang3 = 'org.apache.commons:commons-lang3:3.17.0'
|
||||||
lib_jackson_databind = 'com.fasterxml.jackson.core:jackson-databind:2.14.2'
|
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_core = "org.apache.logging.log4j:log4j-core:${version_log4j2}"
|
||||||
lib_log4j2_slf4j_impl = "org.apache.logging.log4j:log4j-slf4j-impl:${version_log4j2}"
|
lib_log4j2_slf4j_impl = "org.apache.logging.log4j:log4j-slf4j-impl:${version_log4j2}"
|
||||||
@@ -73,6 +73,11 @@ subprojects {
|
|||||||
url 'https://repo.lucares.de/'
|
url 'https://repo.lucares.de/'
|
||||||
content { includeGroup "org.lucares" }
|
content { includeGroup "org.lucares" }
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
url "https://nexus.disco.lab/repository/maven-all/"
|
||||||
|
allowInsecureProtocol = true
|
||||||
|
content { excludeGroup "org.lucares" }
|
||||||
|
}
|
||||||
mavenCentral(content: { excludeGroup "org.lucares" })
|
mavenCentral(content: { excludeGroup "org.lucares" })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,5 +141,5 @@ subprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
gradleVersion = '8.0.2'
|
gradleVersion = '8.10.2'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
34
gradlew
vendored
34
gradlew
vendored
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -83,10 +85,9 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# 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
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
' "$PWD" ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,10 +134,13 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
@@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# 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 ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
@@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# 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 -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|||||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
liquibase.hub.mode=off
|
|
||||||
@@ -18,12 +18,15 @@
|
|||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"architect": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "build/generated/resources",
|
"outputPath": {
|
||||||
|
"base": "build/generated/resources"
|
||||||
|
},
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"polyfills": [
|
||||||
"polyfills": "src/polyfills.ts",
|
"src/polyfills.ts"
|
||||||
|
],
|
||||||
"tsConfig": "tsconfig.app.json",
|
"tsConfig": "tsconfig.app.json",
|
||||||
"inlineStyleLanguage": "scss",
|
"inlineStyleLanguage": "scss",
|
||||||
"assets": [
|
"assets": [
|
||||||
@@ -35,7 +38,8 @@
|
|||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/marked/marked.min.js"
|
"node_modules/marked/marked.min.js"
|
||||||
]
|
],
|
||||||
|
"browser": "src/main.ts"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
@@ -57,12 +61,11 @@
|
|||||||
"with": "src/environments/environment.prod.ts"
|
"with": "src/environments/environment.prod.ts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all",
|
||||||
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildOptimizer": false,
|
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"vendorChunk": true,
|
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"namedChunks": true
|
"namedChunks": true
|
||||||
@@ -77,10 +80,10 @@
|
|||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "pdb-js:build:production"
|
"buildTarget": "pdb-js:build:production"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"browserTarget": "pdb-js:build:development"
|
"buildTarget": "pdb-js:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
@@ -88,7 +91,7 @@
|
|||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "pdb-js:build"
|
"buildTarget": "pdb-js:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
@@ -111,5 +114,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
plugins {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
24620
pdb-js/package-lock.json
generated
24620
pdb-js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,41 +9,44 @@
|
|||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"releasebuild": "ng build --configuration production"
|
"releasebuild": "ng build --configuration production",
|
||||||
|
"explore": "source-map-explorer build/generated/resources/**/*.js"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^15.0.2",
|
"@angular/animations": "^18.2.6",
|
||||||
"@angular/cdk": "^15.0.1",
|
"@angular/cdk": "^18.2.6",
|
||||||
"@angular/common": "^15.0.2",
|
"@angular/common": "^18.2.6",
|
||||||
"@angular/compiler": "^15.0.2",
|
"@angular/compiler": "^18.2.6",
|
||||||
"@angular/core": "^15.0.2",
|
"@angular/core": "^18.2.6",
|
||||||
"@angular/forms": "^15.0.2",
|
"@angular/forms": "^18.2.6",
|
||||||
"@angular/material": "^15.0.1",
|
"@angular/material": "^18.2.6",
|
||||||
"@angular/platform-browser": "^15.0.2",
|
"@angular/platform-browser": "^18.2.6",
|
||||||
"@angular/platform-browser-dynamic": "^15.0.2",
|
"@angular/platform-browser-dynamic": "^18.2.6",
|
||||||
"@angular/router": "^15.0.2",
|
"@angular/router": "^18.2.6",
|
||||||
"marked": "^4.2.12",
|
"luxon": "^3.4.3",
|
||||||
"moment": "^2.29.1",
|
"marked": "^12",
|
||||||
"ngx-markdown": "^15.1.2",
|
"ngx-markdown": "18.0.0",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"rxjs-compat": "^6.6.7",
|
"rxjs-compat": "^6.6.7",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "^0.14.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^15.0.2",
|
"@angular-devkit/build-angular": "^18.2.6",
|
||||||
"@angular/cli": "^15.0.2",
|
"@angular/cli": "^18.2.6",
|
||||||
"@angular/compiler-cli": "^15.0.2",
|
"@angular/compiler-cli": "^18.2.6",
|
||||||
"@types/jasmine": "~3.10.0",
|
"@types/jasmine": "~4.3.0",
|
||||||
|
"@types/luxon": "^3.3.2",
|
||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
"jasmine-core": "~3.10.0",
|
"jasmine-core": "~4.6.0",
|
||||||
"karma": "~6.3.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage": "~2.1.0",
|
"karma-coverage": "~2.2.0",
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~1.7.0",
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
"typescript": "4.8"
|
"source-map-explorer": "^2.5.3",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
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';
|
|
||||||
|
|
||||||
|
|
||||||
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: '**', component: PageNotFoundComponent }
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule.forRoot(routes, {})
|
|
||||||
],
|
|
||||||
declarations: [],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
|
||||||
export class AppRoutingModule { }
|
|
||||||
@@ -5,13 +5,11 @@ import { AppComponent } from './app.component';
|
|||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterTestingModule
|
RouterTestingModule,
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
AppComponent
|
||||||
],
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should create the app', () => {
|
it('should create the app', () => {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import {DomSanitizer} from '@angular/platform-browser';
|
import {DomSanitizer} from '@angular/platform-browser';
|
||||||
import {MatIconRegistry} from '@angular/material/icon';
|
import {MatIconRegistry} from '@angular/material/icon';
|
||||||
|
import { MatAnchor } from '@angular/material/button';
|
||||||
|
import { RouterLink, RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatAnchor, RouterLink, RouterOutlet]
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'pdb';
|
title = 'pdb';
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
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 { 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 {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 {DragDropModule} from '@angular/cdk/drag-drop';
|
|
||||||
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
|
||||||
import { FocusDirective } from './focus.directive';
|
|
||||||
import { MarkdownModule } from 'ngx-markdown';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
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
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
MarkdownModule.forRoot(),
|
|
||||||
BrowserModule,
|
|
||||||
AppRoutingModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
DragDropModule,
|
|
||||||
MatAutocompleteModule,
|
|
||||||
MatBadgeModule,
|
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
MatDialogModule,
|
|
||||||
MatFormFieldModule,
|
|
||||||
MatGridListModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatRadioModule,
|
|
||||||
MatProgressBarModule,
|
|
||||||
MatProgressSpinnerModule,
|
|
||||||
MatSelectModule,
|
|
||||||
MatSnackBarModule,
|
|
||||||
MatTableModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
BrowserAnimationsModule,
|
|
||||||
HttpClientModule
|
|
||||||
],
|
|
||||||
providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],
|
|
||||||
bootstrap: [AppComponent]
|
|
||||||
})
|
|
||||||
export class AppModule { }
|
|
||||||
|
|
||||||
enableProdMode()
|
|
||||||
188
pdb-js/src/app/components/datepicker/date-picker.component.html
Normal file
188
pdb-js/src/app/components/datepicker/date-picker.component.html
Normal 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>
|
||||||
198
pdb-js/src/app/components/datepicker/date-picker.component.ts
Normal file
198
pdb-js/src/app/components/datepicker/date-picker.component.ts
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
forwardRef,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
} from "@angular/core";
|
||||||
|
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, Validators, FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
import { MatButton } from "@angular/material/button";
|
||||||
|
import { MatTooltip } from "@angular/material/tooltip";
|
||||||
|
import { CdkOverlayOrigin, CdkConnectedOverlay } from "@angular/cdk/overlay";
|
||||||
|
import { MatTabGroup, MatTab } from "@angular/material/tabs";
|
||||||
|
import { MatFormField, MatLabel } from "@angular/material/form-field";
|
||||||
|
import { MatInput } from "@angular/material/input";
|
||||||
|
|
||||||
|
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",
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => DatePickerComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatButton,
|
||||||
|
MatTooltip,
|
||||||
|
CdkOverlayOrigin,
|
||||||
|
CdkConnectedOverlay,
|
||||||
|
MatTabGroup,
|
||||||
|
MatTab,
|
||||||
|
MatFormField,
|
||||||
|
MatLabel,
|
||||||
|
MatInput,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ describe('ConfirmationDialogComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ ConfirmationDialogComponent ]
|
imports: [ConfirmationDialogComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(ConfirmationDialogComponent);
|
fixture = TestBed.createComponent(ConfirmationDialogComponent);
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose } from '@angular/material/dialog';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { FocusDirective } from '../focus.directive';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-confirmation-dialog',
|
selector: 'app-confirmation-dialog',
|
||||||
templateUrl: './confirmation-dialog.component.html'
|
templateUrl: './confirmation-dialog.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgIf, MatDialogTitle, CdkScrollable, MatDialogContent, MatDialogActions, MatButton, MatDialogClose, FocusDirective]
|
||||||
})
|
})
|
||||||
export class ConfirmationDialogComponent {
|
export class ConfirmationDialogComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('CustomizableGridComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ CustomizableGridComponent ]
|
imports: [CustomizableGridComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(CustomizableGridComponent);
|
fixture = TestBed.createComponent(CustomizableGridComponent);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { CdkDragEnter, CdkDropList, moveItemInArray, DragRef} from '@angular/cdk/drag-drop';
|
import { CdkDragEnter, CdkDropList, moveItemInArray, DragRef, CdkDropListGroup, CdkDrag } from '@angular/cdk/drag-drop';
|
||||||
import { AfterViewInit } from '@angular/core';
|
import { AfterViewInit } from '@angular/core';
|
||||||
import { ViewChild } from '@angular/core';
|
import { ViewChild } from '@angular/core';
|
||||||
|
import { NgFor, NgStyle, NgClass } from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-customizable-grid',
|
selector: 'app-customizable-grid',
|
||||||
templateUrl: './customizable-grid.component.html'
|
templateUrl: './customizable-grid.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgFor, CdkDropListGroup, NgStyle, CdkDropList, CdkDrag, NgClass]
|
||||||
})
|
})
|
||||||
export class CustomizableGridComponent implements AfterViewInit {
|
export class CustomizableGridComponent implements AfterViewInit {
|
||||||
@ViewChild(CdkDropList) placeholder!: CdkDropList;
|
@ViewChild(CdkDropList) placeholder!: CdkDropList;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('DashboardPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ DashboardPageComponent ]
|
imports: [DashboardPageComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(DashboardPageComponent);
|
fixture = TestBed.createComponent(DashboardPageComponent);
|
||||||
|
|||||||
@@ -5,10 +5,17 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component';
|
||||||
import { Dashboard, DashboardCreationData, DashboardList, DashboardService } from '../dashboard.service';
|
import { Dashboard, DashboardCreationData, DashboardList, DashboardService } from '../dashboard.service';
|
||||||
import { NewDashboardComponent } from './new-dashboard/new-dashboard.component';
|
import { NewDashboardComponent } from './new-dashboard/new-dashboard.component';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||||
|
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||||
|
import { MatTable, MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell, MatHeaderRowDef, MatHeaderRow, MatRowDef, MatRow } from '@angular/material/table';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard-page',
|
selector: 'app-dashboard-page',
|
||||||
templateUrl: './dashboard-page.component.html'
|
templateUrl: './dashboard-page.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgIf, MatProgressSpinner, MatButton, MatTable, MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell, RouterLink, MatIconButton, MatHeaderRowDef, MatHeaderRow, MatRowDef, MatRow]
|
||||||
})
|
})
|
||||||
export class DashboardPageComponent implements OnInit {
|
export class DashboardPageComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -18,5 +18,5 @@
|
|||||||
|
|
||||||
<div mat-dialog-actions align="end">
|
<div mat-dialog-actions align="end">
|
||||||
<button mat-button mat-dialog-close >Cancel</button>
|
<button mat-button mat-dialog-close >Cancel</button>
|
||||||
<button class="save-button" mat-button mat-dialog-close (click)="onSaveClick()">Save</button>
|
<button class="save-button" mat-flat-button mat-dialog-close (click)="onSaveClick()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('AddPlotDialogComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ AddPlotDialogComponent ]
|
imports: [AddPlotDialogComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AddPlotDialogComponent);
|
fixture = TestBed.createComponent(AddPlotDialogComponent);
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogContent, MatDialogActions, MatDialogClose } from '@angular/material/dialog';
|
||||||
import { PlotConfig } from 'src/app/plot.service';
|
import { PlotConfig } from 'src/app/plot.service';
|
||||||
import { VisualizationPageComponent } from 'src/app/visualization-page/visualization-page.component';
|
import { VisualizationPageComponent } from 'src/app/visualization-page/visualization-page.component';
|
||||||
|
import { VisualizationPageComponent as VisualizationPageComponent_1 } from '../../../visualization-page/visualization-page.component';
|
||||||
|
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-plot-dialog',
|
selector: 'app-add-plot-dialog',
|
||||||
templateUrl: './add-plot-dialog.component.html'
|
templateUrl: './add-plot-dialog.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [VisualizationPageComponent_1, CdkScrollable, MatDialogContent, MatDialogActions, MatButton, MatDialogClose]
|
||||||
})
|
})
|
||||||
export class AddPlotDialogComponent {
|
export class AddPlotDialogComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -34,5 +34,5 @@
|
|||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions align="end">
|
<div mat-dialog-actions align="end">
|
||||||
<button mat-button mat-dialog-close (click)="close()">Cancel</button>
|
<button mat-button mat-dialog-close (click)="close()">Cancel</button>
|
||||||
<button class="save-button" mat-button mat-dialog-close (click)="onSaveClick()">Save</button>
|
<button class="save-button" mat-flat-button mat-dialog-close (click)="onSaveClick()">Save</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('AddTextDialogComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ AddTextDialogComponent ]
|
imports: [AddTextDialogComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(AddTextDialogComponent);
|
fixture = TestBed.createComponent(AddTextDialogComponent);
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose } from '@angular/material/dialog';
|
||||||
|
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||||
|
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { FocusDirective } from '../../../focus.directive';
|
||||||
|
import { MarkdownComponent } from 'ngx-markdown';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-add-text-dialog',
|
selector: 'app-add-text-dialog',
|
||||||
templateUrl: './add-text-dialog.component.html'
|
templateUrl: './add-text-dialog.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatDialogTitle, CdkScrollable, MatDialogContent, MatFormField, MatLabel, MatInput, FormsModule, FocusDirective, MarkdownComponent, MatDialogActions, MatButton, MatDialogClose]
|
||||||
})
|
})
|
||||||
export class AddTextDialogComponent {
|
export class AddTextDialogComponent {
|
||||||
text = "";
|
text = "";
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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 {
|
.center {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -37,6 +47,9 @@
|
|||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
.editable {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.editable-hovered {
|
.editable-hovered {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -72,13 +85,15 @@
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<button mat-button (click)="addText()">Add Text</button>
|
<button mat-button (click)="addText()">Add Text</button>
|
||||||
<button mat-button (click)="addPlot()">Add Plot</button>
|
<button mat-button (click)="addPlot()">Add Plot</button>
|
||||||
<button class="save-button" mat-button (click)="save()" [disabled]="!isDirty()">Save</button>
|
<button class="save-button" mat-flat-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>
|
||||||
<div class="editable">
|
<div class="editable">
|
||||||
<h1>{{dashboard.name}}<button mat-icon-button (click)="editNameAndDescription()" class="editable-hovered"><img src="/assets/img/edit-outline.svg"/></button></h1>
|
<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>
|
<p>{{dashboard.description}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div cdkDropListGroup class="dashboard-area">
|
<div cdkDropListGroup class="dashboard-area">
|
||||||
<div
|
<div
|
||||||
cdkDropList
|
cdkDropList
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('DashboardComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ DashboardComponent ]
|
imports: [DashboardComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(DashboardComponent);
|
fixture = TestBed.createComponent(DashboardComponent);
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
|
import { CdkDragDrop, moveItemInArray, transferArrayItem, CdkDropListGroup, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
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 { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
import { Dashboard, DashboardCreationData, DashboardService, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service';
|
import { Dashboard, DashboardCreationData, DashboardService, PlotWidget, PlotWidgetRenderData, TextWidget } from 'src/app/dashboard.service';
|
||||||
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
||||||
import { NewDashboardComponent } from '../new-dashboard/new-dashboard.component';
|
import { NewDashboardComponent } from '../new-dashboard/new-dashboard.component';
|
||||||
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
|
import { AddPlotDialogComponent } from './add-plot-dialog/add-plot-dialog.component';
|
||||||
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
|
import { AddTextDialogComponent } from './add-text-dialog/add-text-dialog.component';
|
||||||
|
import { DatePickerChange, DatePickerComponent } from 'src/app/components/datepicker/date-picker.component';
|
||||||
|
import { NgIf, NgFor } from '@angular/common';
|
||||||
|
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||||
|
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||||
|
import { DatePickerComponent as DatePickerComponent_1 } from '../../components/datepicker/date-picker.component';
|
||||||
|
import { TextWidgetComponent } from './text-widget/text-widget.component';
|
||||||
|
import { PlotWidgetComponent } from './plot-widget/plot-widget.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
templateUrl: './dashboard.component.html'
|
templateUrl: './dashboard.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgIf, MatProgressSpinner, RouterLink, MatButton, DatePickerComponent_1, MatIconButton, CdkDropListGroup, NgFor, CdkDropList, CdkDrag, CdkDragHandle, TextWidgetComponent, PlotWidgetComponent]
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit{
|
||||||
|
|
||||||
dashboard?: Dashboard = undefined;
|
dashboard?: Dashboard = undefined;
|
||||||
|
|
||||||
@@ -24,6 +33,9 @@ export class DashboardComponent implements OnInit {
|
|||||||
|
|
||||||
plotWidgetRenderData: PlotWidgetRenderData[] = [];
|
plotWidgetRenderData: PlotWidgetRenderData[] = [];
|
||||||
|
|
||||||
|
@ViewChild("datePicker")
|
||||||
|
datePicker!: DatePickerComponent;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private service: DashboardService,
|
private service: DashboardService,
|
||||||
@@ -58,6 +70,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() {
|
isDirty() {
|
||||||
return this.pristineDashboardJSON !== JSON.stringify(this.dashboard);
|
return this.pristineDashboardJSON !== JSON.stringify(this.dashboard);
|
||||||
}
|
}
|
||||||
@@ -70,10 +89,13 @@ export class DashboardComponent implements OnInit {
|
|||||||
if (plot.isAborted) {
|
if (plot.isAborted) {
|
||||||
this.loadImages(index +1 , plotWidgetQueue);
|
this.loadImages(index +1 , plotWidgetQueue);
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
|
plot.plotResponse = undefined; // remove old image and show loading icon
|
||||||
|
|
||||||
const request = PlotWidget.createPlotRequest(plot.widget, plot.submitterId);
|
const request = PlotWidget.createPlotRequest(plot.widget, plot.submitterId);
|
||||||
this.plotService.sendPlotRequest(request).subscribe({
|
this.plotService.sendPlotRequest(request).subscribe({
|
||||||
next: (response: PlotResponse)=> {
|
next: (response: PlotResponse)=> {
|
||||||
plot.plotResponse= response;
|
plot.plotResponse = response;
|
||||||
},
|
},
|
||||||
error: (error:any)=> {
|
error: (error:any)=> {
|
||||||
plot.error = error;
|
plot.error = error;
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogClose } from '@angular/material/dialog';
|
||||||
|
import { MatIconButton } from '@angular/material/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-full-screen-plot-dialog',
|
selector: 'app-full-screen-plot-dialog',
|
||||||
templateUrl: './full-screen-plot-dialog.component.html'
|
templateUrl: './full-screen-plot-dialog.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatIconButton, MatDialogClose]
|
||||||
})
|
})
|
||||||
export class FullScreenPlotDialogComponent {
|
export class FullScreenPlotDialogComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('PlotWidgetComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ PlotWidgetComponent ]
|
imports: [PlotWidgetComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(PlotWidgetComponent);
|
fixture = TestBed.createComponent(PlotWidgetComponent);
|
||||||
|
|||||||
@@ -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 { MatDialog } from '@angular/material/dialog';
|
||||||
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service';
|
import { PlotWidget, PlotWidgetRenderData } from 'src/app/dashboard.service';
|
||||||
@@ -6,10 +6,14 @@ import { PlotViewComponent } from 'src/app/plot-view/plot-view.component';
|
|||||||
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
import { PlotConfig, PlotResponse, PlotService } from 'src/app/plot.service';
|
||||||
import { AddPlotDialogComponent } from '../add-plot-dialog/add-plot-dialog.component';
|
import { AddPlotDialogComponent } from '../add-plot-dialog/add-plot-dialog.component';
|
||||||
import { FullScreenPlotDialogComponent } from '../full-screen-plot-dialog/full-screen-plot-dialog.component';
|
import { FullScreenPlotDialogComponent } from '../full-screen-plot-dialog/full-screen-plot-dialog.component';
|
||||||
|
import { NgClass, NgIf } from '@angular/common';
|
||||||
|
import { MatIconButton, MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-plot-widget',
|
selector: 'app-plot-widget',
|
||||||
templateUrl: './plot-widget.component.html'
|
templateUrl: './plot-widget.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgClass, MatIconButton, NgIf, MatButton]
|
||||||
})
|
})
|
||||||
export class PlotWidgetComponent {
|
export class PlotWidgetComponent {
|
||||||
@Input("data")
|
@Input("data")
|
||||||
@@ -18,14 +22,13 @@ export class PlotWidgetComponent {
|
|||||||
public thumbnailUrl = "";
|
public thumbnailUrl = "";
|
||||||
|
|
||||||
|
|
||||||
@ViewChild("plotView") plotView!: PlotViewComponent;
|
//@ViewChild("plotView") plotView!: PlotViewComponent;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
deleted : EventEmitter<string> = new EventEmitter<string>();
|
deleted : EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(private dialog: MatDialog, private service: PlotService){}
|
constructor(private dialog: MatDialog, private service: PlotService){}
|
||||||
|
|
||||||
|
|
||||||
hasRender(name: string): boolean{
|
hasRender(name: string): boolean{
|
||||||
return this.data !== undefined && this.data.plotResponse !== undefined && this.data.plotResponse?.rendered[name] !== undefined;
|
return this.data !== undefined && this.data.plotResponse !== undefined && this.data.plotResponse?.rendered[name] !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('TextWidgetComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ TextWidgetComponent ]
|
imports: [TextWidgetComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(TextWidgetComponent);
|
fixture = TestBed.createComponent(TextWidgetComponent);
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from 'src/app/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { TextWidget } from 'src/app/dashboard.service';
|
import { TextWidget } from 'src/app/dashboard.service';
|
||||||
import { AddTextDialogComponent } from '../add-text-dialog/add-text-dialog.component';
|
import { AddTextDialogComponent } from '../add-text-dialog/add-text-dialog.component';
|
||||||
|
import { MatIconButton } from '@angular/material/button';
|
||||||
|
import { MarkdownComponent } from 'ngx-markdown';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-text-widget',
|
selector: 'app-text-widget',
|
||||||
templateUrl: './text-widget.component.html'
|
templateUrl: './text-widget.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatIconButton, MarkdownComponent]
|
||||||
})
|
})
|
||||||
export class TextWidgetComponent {
|
export class TextWidgetComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('NewDashboardComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ NewDashboardComponent ]
|
imports: [NewDashboardComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(NewDashboardComponent);
|
fixture = TestBed.createComponent(NewDashboardComponent);
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
|
||||||
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialogClose } from '@angular/material/dialog';
|
||||||
import { DashboardCreationData } from 'src/app/dashboard.service';
|
import { DashboardCreationData } from 'src/app/dashboard.service';
|
||||||
import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
|
import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { CdkScrollable } from '@angular/cdk/scrolling';
|
||||||
|
import { MatFormField, MatLabel, MatError } from '@angular/material/form-field';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
import { FocusDirective } from '../../focus.directive';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-new-dashboard',
|
selector: 'app-new-dashboard',
|
||||||
templateUrl: './new-dashboard.component.html'
|
templateUrl: './new-dashboard.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [FormsModule, ReactiveFormsModule, MatDialogTitle, CdkScrollable, MatDialogContent, MatFormField, MatLabel, MatInput, FocusDirective, MatError, MatDialogActions, MatButton, MatDialogClose]
|
||||||
})
|
})
|
||||||
export class NewDashboardComponent implements OnInit {
|
export class NewDashboardComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
|
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[focus]'
|
selector: '[focus]',
|
||||||
|
standalone: true
|
||||||
})
|
})
|
||||||
export class FocusDirective implements AfterViewInit {
|
export class FocusDirective implements AfterViewInit {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('GalleryViewComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ GalleryViewComponent ]
|
imports: [GalleryViewComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
import { Component, OnInit, Input, Output, ViewChild, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, ViewChild, EventEmitter, forwardRef } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { PlotService, PlotRequest, PlotResponse, PlotResponseStats, DashTypeAndColor, RenderedImages } from '../plot.service';
|
import { PlotService, PlotRequest, PlotResponse, PlotResponseStats, DashTypeAndColor, RenderedImages } from '../plot.service';
|
||||||
import { UtilService } from '../utils.service';
|
import { UtilService } from '../utils.service';
|
||||||
|
import { NgIf, NgClass, NgFor } from '@angular/common';
|
||||||
|
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||||
|
import { MatSelect } from '@angular/material/select';
|
||||||
|
import { MatOption } from '@angular/material/core';
|
||||||
|
import { ImageToggleComponent } from '../image-toggle/image-toggle.component';
|
||||||
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { MatProgressBar } from '@angular/material/progress-bar';
|
||||||
|
import { MatButton } from '@angular/material/button';
|
||||||
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
|
import { PlotDetailsComponent } from '../plot-details/plot-details.component';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
|
||||||
export class GalleryFilterData {
|
export class GalleryFilterData {
|
||||||
filterBy :string;
|
filterBy :string;
|
||||||
@@ -17,9 +29,11 @@ export class GalleryFilterData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-gallery-filter-view',
|
selector: 'pdb-gallery-filter-view',
|
||||||
templateUrl: './gallery-filter-view.component.html',
|
templateUrl: './gallery-filter-view.component.html',
|
||||||
styleUrls: ['./gallery-filter-view.component.scss']
|
styleUrls: ['./gallery-filter-view.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatFormField, MatLabel, MatSelect, MatOption, NgIf, ImageToggleComponent, MatInput, FormsModule]
|
||||||
})
|
})
|
||||||
export class GalleryFilterView {
|
export class GalleryFilterView {
|
||||||
compareImages = JSON.stringify([
|
compareImages = JSON.stringify([
|
||||||
@@ -89,9 +103,11 @@ export class GalleryFilterView {
|
|||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-gallery-view',
|
selector: 'pdb-gallery-view',
|
||||||
templateUrl: './gallery-view.component.html',
|
templateUrl: './gallery-view.component.html',
|
||||||
styleUrls: ['./gallery-view.component.scss']
|
styleUrls: ['./gallery-view.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgIf, MatFormField, MatLabel, MatSelect, MatOption, ImageToggleComponent, GalleryFilterView, MatCheckbox, FormsModule, MatProgressBar, MatButton, MatTooltip, NgClass, NgFor, forwardRef(() => GalleryItemView)]
|
||||||
})
|
})
|
||||||
export class GalleryViewComponent implements OnInit {
|
export class GalleryViewComponent implements OnInit {
|
||||||
|
|
||||||
@@ -313,9 +329,11 @@ export class GalleryViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-gallery-item-view',
|
selector: 'pdb-gallery-item-view',
|
||||||
templateUrl: './gallery-item-view.component.html',
|
templateUrl: './gallery-item-view.component.html',
|
||||||
styleUrls: ['./gallery-item-view.component.scss']
|
styleUrls: ['./gallery-item-view.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgClass, NgIf, PlotDetailsComponent]
|
||||||
})
|
})
|
||||||
export class GalleryItemView {
|
export class GalleryItemView {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('HelpPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ HelpPageComponent ]
|
imports: [HelpPageComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-help-page',
|
selector: 'pdb-help-page',
|
||||||
templateUrl: './help-page.component.html',
|
templateUrl: './help-page.component.html',
|
||||||
styleUrls: ['./help-page.component.scss']
|
styleUrls: ['./help-page.component.scss'],
|
||||||
|
standalone: true
|
||||||
})
|
})
|
||||||
export class HelpPageComponent implements OnInit {
|
export class HelpPageComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('ImageToggleComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ ImageToggleComponent ]
|
imports: [ImageToggleComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-image-toggle',
|
selector: 'pdb-image-toggle',
|
||||||
templateUrl: './image-toggle.component.html',
|
templateUrl: './image-toggle.component.html',
|
||||||
styleUrls: ['./image-toggle.component.scss']
|
styleUrls: ['./image-toggle.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [NgIf]
|
||||||
})
|
})
|
||||||
export class ImageToggleComponent implements OnInit {
|
export class ImageToggleComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('LimitByComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ LimitByComponent ]
|
imports: [LimitByComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import { Component, Input} from '@angular/core';
|
import { Component, Input} from '@angular/core';
|
||||||
import {FormControl} from '@angular/forms';
|
import { FormControl, FormsModule } from '@angular/forms';
|
||||||
|
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||||
|
import { MatSelect } from '@angular/material/select';
|
||||||
|
import { MatOption } from '@angular/material/core';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-limit-by',
|
selector: 'pdb-limit-by',
|
||||||
templateUrl: './limit-by.component.html',
|
templateUrl: './limit-by.component.html',
|
||||||
styleUrls: ['./limit-by.component.scss']
|
styleUrls: ['./limit-by.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatFormField, MatLabel, MatSelect, MatOption, NgIf, MatInput, FormsModule]
|
||||||
})
|
})
|
||||||
export class LimitByComponent {
|
export class LimitByComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('MainPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ MainPageComponent ]
|
imports: [MainPageComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-main-page',
|
selector: 'pdb-main-page',
|
||||||
templateUrl: './main-page.component.html',
|
templateUrl: './main-page.component.html',
|
||||||
styleUrls: ['./main-page.component.scss']
|
styleUrls: ['./main-page.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [RouterLink]
|
||||||
})
|
})
|
||||||
export class MainPageComponent implements OnInit {
|
export class MainPageComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
.gallery-item-details td {
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
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 { DashTypeAndColor, PlotResponseStats, DataSeriesStats } from '../plot.service';
|
||||||
import { UtilService } from '../utils.service';
|
import { UtilService } from '../utils.service';
|
||||||
|
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { NgFor, NgIf } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-plot-details',
|
selector: 'pdb-plot-details',
|
||||||
templateUrl: './plot-details.component.html',
|
templateUrl: './plot-details.component.html',
|
||||||
styleUrls: ['./plot-details.component.scss']
|
styleUrls: ['./plot-details.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatRadioGroup, FormsModule, MatRadioButton, NgFor, NgIf]
|
||||||
})
|
})
|
||||||
export class PlotDetailsComponent {
|
export class PlotDetailsComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
<div
|
||||||
*ngIf="imageUrl">
|
*ngIf="imageUrl">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -20,4 +20,61 @@ img {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: 5px 5px 10px 0px #e0e0e0;
|
box-shadow: 5px 5px 10px 0px #e0e0e0;
|
||||||
overflow: auto;
|
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;
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ describe('PlotViewComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ PlotViewComponent ]
|
imports: [PlotViewComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,34 @@
|
|||||||
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
|
import { Component, Output, EventEmitter } from '@angular/core';
|
||||||
import { DataType, AxesTypes, PlotResponseStats, PlotConfig, PlotService, PlotResponse, PlotRequest, RenderOptions } from '../plot.service';
|
import { DataType, AxesTypes, PlotResponseStats, PlotConfig, PlotService, PlotResponse, PlotRequest, RenderOptions, DataSeriesStats, DashTypeAndColor } from '../plot.service';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import * as moment from 'moment';
|
//import * as moment from 'moment';
|
||||||
import { WidgetDimensions } from '../dashboard.service';
|
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';
|
||||||
|
import { CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
|
||||||
|
import { NgClass, NgFor, NgIf } from '@angular/common';
|
||||||
|
import { PlotDetailsComponent } from '../plot-details/plot-details.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-plot-view',
|
selector: 'pdb-plot-view',
|
||||||
templateUrl: './plot-view.component.html',
|
templateUrl: './plot-view.component.html',
|
||||||
styleUrls: ['./plot-view.component.scss']
|
styleUrls: ['./plot-view.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [CdkDrag, NgClass, CdkDragHandle, NgFor, NgIf, PlotDetailsComponent]
|
||||||
})
|
})
|
||||||
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 gnuplotLMargin = 110; // The left margin configured for gnuplot
|
||||||
readonly gnuplotRMargin = 110; // The right margin configured for gnuplot
|
readonly gnuplotRMargin = 110; // The right margin configured for gnuplot
|
||||||
readonly gnuplotTMargin = 57; // The top margin configured for gnuplot
|
readonly gnuplotTMargin = 57; // The top margin configured for gnuplot
|
||||||
readonly gnuplotBMargin = 76; // The bottom margin configured for gnuplot
|
readonly gnuplotBMargin = 76; // The bottom margin configured for gnuplot
|
||||||
|
|
||||||
|
isOpen = false;
|
||||||
|
|
||||||
imageUrl! : string;
|
imageUrl! : string;
|
||||||
stats: PlotResponseStats | null = null;
|
stats: PlotResponseStats | null = null;
|
||||||
@@ -28,7 +39,7 @@ export class PlotViewComponent implements OnInit {
|
|||||||
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
loadingEvent : EventEmitter<LoadingEvent> = new EventEmitter<LoadingEvent>();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dateRangeUpdateEvent : EventEmitter<string> = new EventEmitter<string>();
|
dateRangeUpdateEvent : EventEmitter<DateValue> = new EventEmitter<DateValue>();
|
||||||
|
|
||||||
in_drag_mode = false;
|
in_drag_mode = false;
|
||||||
drag_start_x = 0;
|
drag_start_x = 0;
|
||||||
@@ -47,10 +58,9 @@ export class PlotViewComponent implements OnInit {
|
|||||||
|
|
||||||
config? : PlotConfig;
|
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) {
|
showError(message:string) {
|
||||||
@@ -177,12 +187,12 @@ export class PlotViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDateRange(startDate: any, endDate: any) {
|
setDateRange(startDate: any, endDate: any) {
|
||||||
const formattedStartDate = startDate.format(this.DATE_PATTERN);
|
const formattedStartDate = startDate.toFormat(this.DATE_PATTERN);
|
||||||
const formattedEndDate = endDate.format(this.DATE_PATTERN);
|
const formattedEndDate = endDate.toFormat(this.DATE_PATTERN);
|
||||||
|
|
||||||
const newDateRange = formattedStartDate+" - "+formattedEndDate;
|
const newDateRange = formattedStartDate+" - "+formattedEndDate;
|
||||||
|
const newDateValue = new DateValue('ABSOLUTE', newDateRange, newDateRange);
|
||||||
this.dateRangeUpdateEvent.emit(newDateRange);
|
this.dateRangeUpdateEvent.emit(newDateValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomRange(range: SelectionRange) {
|
zoomRange(range: SelectionRange) {
|
||||||
@@ -283,7 +293,7 @@ export class PlotViewComponent implements OnInit {
|
|||||||
'main': new RenderOptions(actualDimension.height, actualDimension.width, false, true)
|
'main': new RenderOptions(actualDimension.height, actualDimension.width, false, true)
|
||||||
});
|
});
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zoom in/out by zoomFaktor, so that the anchorInPercentOfDateRange keeps the same position.
|
* Zoom in/out by zoomFaktor, so that the anchorInPercentOfDateRange keeps the same position.
|
||||||
@@ -291,18 +301,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.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(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 dateRangeParsed = this.parseDateRange(dateValue);
|
||||||
const dateRangeInSeconds = dateRangeParsed.duration.asSeconds();
|
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 newDateRangeInSeconds = dateRangeInSeconds * zoomFactor;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const newStartDate = anchorTimestampInSeconds.clone().subtract(newDateRangeInSeconds*anchorInPercentOfDateRange, "seconds");
|
|
||||||
const newEndDate = newStartDate.clone().add({seconds: newDateRangeInSeconds});;
|
|
||||||
|
|
||||||
this.setDateRange(newStartDate, newEndDate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,27 +332,48 @@ export class PlotViewComponent implements OnInit {
|
|||||||
* shiftDate(dateRangeAsString, -0.5, -0.5) will move the range by half its size to older values
|
* 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(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);
|
this.parseDateRange(dateValue).subscribe(
|
||||||
const dateRangeInSeconds = dateRangeParsed.duration.asSeconds();
|
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);
|
|
||||||
|
this.setDateRange(newStartDate, newEndDate);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseDateRange(dateRangeAsString : string) : DateRange {
|
parseDateRange(dateValue : DateValue) : Observable<DateRange> {
|
||||||
const startDate = moment(dateRangeAsString.slice(0, 19));
|
return this.service.toDateRange(dateValue);
|
||||||
const endDate = moment(dateRangeAsString.slice(22, 41));
|
/*
|
||||||
|
.pipe(map((dateRangeAsString:string) => {
|
||||||
return {
|
const startDate = DateTime.fromFormat(dateRangeAsString.slice(0, 19), this.DATE_PATTERN );
|
||||||
startDate: startDate,
|
const endDate = DateTime.fromFormat(dateRangeAsString.slice(22, 41), this.DATE_PATTERN );
|
||||||
endDate: endDate,
|
|
||||||
duration: moment.duration(endDate.diff(startDate))
|
|
||||||
};
|
return {
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SelectionRange {
|
export class SelectionRange {
|
||||||
@@ -362,7 +401,8 @@ export class LoadingEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DateRange {
|
export class DateRange {
|
||||||
startDate: any;
|
constructor(
|
||||||
endDate: any;
|
public startDate: DateTime,
|
||||||
duration: any;
|
public endDate: DateTime,
|
||||||
|
public duration: Duration){}
|
||||||
}
|
}
|
||||||
@@ -1,190 +1,371 @@
|
|||||||
import { Injectable, OnInit } from '@angular/core';
|
import { Injectable, OnInit } from "@angular/core";
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from "@angular/common/http";
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from "rxjs";
|
||||||
import { map } from 'rxjs/operators';
|
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({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class PlotService {
|
export class PlotService {
|
||||||
|
|
||||||
|
readonly DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
|
||||||
|
|
||||||
plotTypes: Array<PlotType>;
|
plotTypes: Array<PlotType>;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
this.plotTypes = new Array<PlotType>();
|
this.plotTypes = new Array<PlotType>();
|
||||||
this.plotTypes.push(new PlotType("SCATTER","Scatter","scatter-chart2",true,DataType.Time,DataType.Duration));
|
this.plotTypes.push(
|
||||||
this.plotTypes.push(new PlotType("CUM_DISTRIBUTION", "Cumulative Distribution", "cumulative-distribution-chart", true, DataType.Percent, DataType.Duration));
|
new PlotType(
|
||||||
this.plotTypes.push(new PlotType("HISTOGRAM", "Histogram", "histogram", true, DataType.HistogramBin, DataType.HistogramCount));
|
"SCATTER",
|
||||||
this.plotTypes.push(new PlotType("PARALLEL", "Parallel Requests", "parallel-requests-chart", true, DataType.Time, DataType.Count));
|
"Scatter",
|
||||||
this.plotTypes.push(new PlotType("BAR", "Bar (number of requests)", "bar-chart", true, DataType.Group, DataType.Count));
|
"scatter-chart2",
|
||||||
this.plotTypes.push(new PlotType("BOX", "Box", "box-plot", true, DataType.Time, DataType.Duration));
|
true,
|
||||||
|
DataType.Time,
|
||||||
this.plotTypes.push(new PlotType("HEATMAP", "Heatmap", "heatmap", false, DataType.Other, DataType.Other));
|
DataType.Duration,
|
||||||
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(
|
||||||
this.plotTypes.push(new PlotType("VIOLIN", "Violin", "violin-chart", false, DataType.Group, DataType.Duration));
|
new PlotType(
|
||||||
this.plotTypes.push(new PlotType("STRIP", "Strip", "strip-chart", false, DataType.Group, DataType.Duration));
|
"CUM_DISTRIBUTION",
|
||||||
this.plotTypes.push(new PlotType("PIE", "Pie", "pie-chart", false, DataType.Other, DataType.Other));
|
"Cumulative Distribution",
|
||||||
this.plotTypes.push(new PlotType("STEP_FIT", "Step Fit", "step-fit", false, DataType.Other, DataType.Other));
|
"cumulative-distribution-chart",
|
||||||
this.plotTypes.push(new PlotType("LAG", "Lag", "lag-plot", false, DataType.Other, DataType.Other));
|
true,
|
||||||
this.plotTypes.push(new PlotType("ACF", "ACF", "acf-plot", false, DataType.Other, DataType.Other));
|
DataType.Percent,
|
||||||
}
|
DataType.Duration,
|
||||||
|
),
|
||||||
getPlotTypes(): Array<PlotType> {
|
);
|
||||||
return this.plotTypes.filter(plotType => plotType.active);
|
this.plotTypes.push(
|
||||||
}
|
new PlotType(
|
||||||
|
"HISTOGRAM",
|
||||||
getTagFields(): Observable<Array<string>> {
|
"Histogram",
|
||||||
return this.http.get<Array<string>>('//'+window.location.hostname+':'+window.location.port+'/api/fields');
|
"histogram",
|
||||||
}
|
true,
|
||||||
|
DataType.HistogramBin,
|
||||||
autocomplete(query: string, caretIndex: number, resultMode: ResultMode): Observable<AutocompleteResult>
|
DataType.HistogramCount,
|
||||||
{
|
),
|
||||||
const options = {
|
);
|
||||||
params: new HttpParams()
|
this.plotTypes.push(
|
||||||
.set('caretIndex', ""+caretIndex)
|
new PlotType(
|
||||||
.set('query', query)
|
"PARALLEL",
|
||||||
.set('resultMode', resultMode)
|
"Parallel Requests",
|
||||||
};
|
"parallel-requests-chart",
|
||||||
return this.http.get<AutocompleteResult>('//'+window.location.hostname+':'+window.location.port+'/api/autocomplete', options);
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
abort(submitterId: string): Observable<void>{
|
getPlotTypes(): Array<PlotType> {
|
||||||
return this.http.delete<void>('//'+window.location.hostname+':'+window.location.port+'/api/plots/'+submitterId)
|
return this.plotTypes.filter((plotType) => plotType.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPlotRequest(plotRequest: PlotRequest): Observable<PlotResponse>{
|
getTagFields(): Observable<Array<string>> {
|
||||||
|
return this.http.get<Array<string>>(
|
||||||
|
"//" + window.location.hostname + ":" + window.location.port +
|
||||||
|
"/api/fields",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
autocomplete(
|
||||||
|
query: string,
|
||||||
|
caretIndex: number,
|
||||||
|
resultMode: ResultMode,
|
||||||
|
): Observable<AutocompleteResult> {
|
||||||
|
const options = {
|
||||||
|
params: new HttpParams()
|
||||||
|
.set("caretIndex", "" + caretIndex)
|
||||||
|
.set("query", query)
|
||||||
|
.set("resultMode", resultMode),
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPlotRequest(plotRequest: PlotRequest): Observable<PlotResponse> {
|
||||||
//console.log("send plot request: "+ JSON.stringify(plotRequest));
|
//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));
|
return result.pipe(map(this.enrichStats));
|
||||||
}
|
}
|
||||||
|
|
||||||
enrichStats(response: PlotResponse): PlotResponse {
|
enrichStats(response: PlotResponse): PlotResponse {
|
||||||
let maxAvgRatio = 0;
|
let maxAvgRatio = 0;
|
||||||
let x : DataSeriesStats[] = response.stats.dataSeriesStats;
|
let x: DataSeriesStats[] = response.stats.dataSeriesStats;
|
||||||
for (const row in x){
|
for (const row in x) {
|
||||||
for (const col in x){
|
for (const col in x) {
|
||||||
maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average);
|
maxAvgRatio = Math.max(maxAvgRatio, x[row].average / x[col].average);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.stats.maxAvgRatio = maxAvgRatio;
|
response.stats.maxAvgRatio = maxAvgRatio;
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterDefaults(): Observable<FilterDefaults>{
|
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)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 {
|
export class PlotType {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public id: string,
|
public id: string,
|
||||||
public name: string,
|
public name: string,
|
||||||
public icon: string,
|
public icon: string,
|
||||||
public active: boolean,
|
public active: boolean,
|
||||||
public xAxis: DataType,
|
public xAxis: DataType,
|
||||||
public yAxis: DataType) {
|
public yAxis: DataType,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
compatible(others: Array<PlotType>) : boolean {
|
compatible(others: Array<PlotType>): boolean {
|
||||||
var xAxisTypes = new Set([this.xAxis]);
|
var xAxisTypes = new Set([this.xAxis]);
|
||||||
var yAxisTypes = new Set([this.yAxis]);
|
var yAxisTypes = new Set([this.yAxis]);
|
||||||
|
|
||||||
for(var i = 0; i < others.length; i++){
|
for (var i = 0; i < others.length; i++) {
|
||||||
var other = others[i];
|
var other = others[i];
|
||||||
xAxisTypes.add(other.xAxis);
|
xAxisTypes.add(other.xAxis);
|
||||||
yAxisTypes.add(other.yAxis);
|
yAxisTypes.add(other.yAxis);
|
||||||
}
|
}
|
||||||
|
|
||||||
return xAxisTypes.size <= 2 && yAxisTypes.size <= 2;
|
return xAxisTypes.size <= 2 && yAxisTypes.size <= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TagField {
|
export class TagField {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
constructor(name: string) {
|
constructor(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DataType {
|
export enum DataType {
|
||||||
Time,
|
Time,
|
||||||
Duration,
|
Duration,
|
||||||
Percent,
|
Percent,
|
||||||
Count,
|
Count,
|
||||||
Group,
|
Group,
|
||||||
Metric,
|
Metric,
|
||||||
HistogramBin,
|
HistogramBin,
|
||||||
HistogramCount,
|
HistogramCount,
|
||||||
Other
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AxesTypes {
|
export class AxesTypes {
|
||||||
x : Array<DataType>;
|
x: Array<DataType>;
|
||||||
y : Array<DataType>;
|
y: Array<DataType>;
|
||||||
|
|
||||||
constructor(x: Array<DataType>, y : Array<DataType>) {
|
constructor(x: Array<DataType>, y: Array<DataType>) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasXAxis(type : DataType){
|
hasXAxis(type: DataType) {
|
||||||
return this.x.includes(type);
|
return this.x.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasYAxis(type : DataType){
|
hasYAxis(type: DataType) {
|
||||||
return this.y.includes(type);
|
return this.y.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the 1-indexed axis data type, e.g. getXAxisDataType(1) for the x1 axis
|
* return the 1-indexed axis data type, e.g. getXAxisDataType(1) for the x1 axis
|
||||||
*/
|
*/
|
||||||
getXAxisDataType(index: number){
|
getXAxisDataType(index: number) {
|
||||||
if (this.x.length+1 >= index){
|
if (this.x.length + 1 >= index) {
|
||||||
return this.x[index-1];
|
return this.x[index - 1];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* return the 1-indexed axis data type, e.g. getYAxisDataType(1) for the x1 axis
|
* return the 1-indexed axis data type, e.g. getYAxisDataType(1) for the x1 axis
|
||||||
*/
|
*/
|
||||||
getYAxisDataType(index: number){
|
getYAxisDataType(index: number) {
|
||||||
if (this.y.length+1 >= index){
|
if (this.y.length + 1 >= index) {
|
||||||
return this.y[index-1];
|
return this.y[index - 1];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
const x1 = this.getXAxisDataType(1);
|
const x1 = this.getXAxisDataType(1);
|
||||||
const y1 = this.getYAxisDataType(1);
|
const y1 = this.getYAxisDataType(1);
|
||||||
const x2 = this.getXAxisDataType(2);
|
const x2 = this.getXAxisDataType(2);
|
||||||
const y2 = this.getYAxisDataType(2);
|
const y2 = this.getYAxisDataType(2);
|
||||||
|
|
||||||
return (x1 ? "x1:"+DataType[x1] : "")
|
return (x1 ? "x1:" + DataType[x1] : "") +
|
||||||
+ (y1 ? " y1:"+DataType[y1] : "")
|
(y1 ? " y1:" + DataType[y1] : "") +
|
||||||
+ (x2 ? " x2:"+DataType[x2] : "")
|
(x2 ? " x2:" + DataType[x2] : "") +
|
||||||
+ (y2 ? " y2:"+DataType[y2] : "");
|
(y2 ? " y2:" + DataType[y2] : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,12 +373,12 @@ export class Suggestion {
|
|||||||
constructor(
|
constructor(
|
||||||
public value: string,
|
public value: string,
|
||||||
public newQuery: string,
|
public newQuery: string,
|
||||||
public newCaretPosition: number){}
|
public newCaretPosition: number,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AutocompleteResult {
|
||||||
export class AutocompleteResult{
|
constructor(public proposals: Array<Suggestion>) {}
|
||||||
constructor(public proposals: Array<Suggestion>){}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderOptionsMap = {
|
export type RenderOptionsMap = {
|
||||||
@@ -212,86 +393,94 @@ export class PlotRequest {
|
|||||||
constructor(
|
constructor(
|
||||||
public submitterId: string,
|
public submitterId: string,
|
||||||
public config: PlotConfig,
|
public config: PlotConfig,
|
||||||
public renders: RenderOptionsMap
|
public renders: RenderOptionsMap,
|
||||||
){}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
copy(): PlotRequest {
|
copy(): PlotRequest {
|
||||||
return JSON.parse(JSON.stringify(this));
|
return JSON.parse(JSON.stringify(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlotConfig {
|
export class PlotConfig {
|
||||||
constructor( public query : string,
|
constructor(
|
||||||
public groupBy : Array<string>,
|
public query: string,
|
||||||
public limitBy : string,
|
public groupBy: Array<string>,
|
||||||
public limit : number,
|
public limitBy: string,
|
||||||
public y1:YAxisDefinition,
|
public limit: number,
|
||||||
public y2:YAxisDefinition|undefined,
|
public y1: YAxisDefinition,
|
||||||
public dateRange : string,
|
public y2: YAxisDefinition | undefined,
|
||||||
public aggregates : Array<string>,
|
public dateRange: DateValue,
|
||||||
|
public aggregates: Array<string>,
|
||||||
public intervalUnit: string,
|
public intervalUnit: string,
|
||||||
public intervalValue: number,
|
public intervalValue: number,
|
||||||
public renderBarChartTickLabels: boolean = false,) {}
|
public renderBarChartTickLabels: boolean = false,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RenderOptions {
|
export class RenderOptions {
|
||||||
constructor(
|
constructor(
|
||||||
public height: number,
|
public height: number,
|
||||||
public width: number,
|
public width: number,
|
||||||
public keyOutside: boolean,
|
public showKey: boolean,
|
||||||
public renderLabels: boolean) {}
|
public renderLabels: boolean,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class YAxisDefinition {
|
export class YAxisDefinition {
|
||||||
constructor(
|
constructor(
|
||||||
public axisScale : string,
|
public axisScale: string,
|
||||||
public rangeMin : number,
|
public rangeMin: number,
|
||||||
public rangeMax : number,
|
public rangeMax: number,
|
||||||
public rangeUnit : string){}
|
public rangeUnit: string,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlotResponse {
|
export class PlotResponse {
|
||||||
constructor(
|
constructor(
|
||||||
public stats : PlotResponseStats,
|
public stats: PlotResponseStats,
|
||||||
public rendered: RenderedImages){}
|
public rendered: RenderedImages,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PlotResponseStats {
|
export class PlotResponseStats {
|
||||||
constructor(
|
constructor(
|
||||||
public maxValue : number,
|
public maxValue: number,
|
||||||
public values : number,
|
public values: number,
|
||||||
public average : number,
|
public average: number,
|
||||||
public plottedValues : number,
|
public plottedValues: number,
|
||||||
public maxAvgRatio: number,
|
public maxAvgRatio: number,
|
||||||
public dataSeriesStats : Array<DataSeriesStats>){}
|
public dataSeriesStats: Array<DataSeriesStats>,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataSeriesStats {
|
export class DataSeriesStats {
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
public values : number,
|
public values: number,
|
||||||
public maxValue : number,
|
public maxValue: number,
|
||||||
public average : number ,
|
public average: number,
|
||||||
public plottedValues : number,
|
public plottedValues: number,
|
||||||
public dashTypeAndColor: DashTypeAndColor,
|
public dashTypeAndColor: DashTypeAndColor,
|
||||||
public percentiles: Map<string, number>){}
|
public percentiles: Map<string, number>,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashTypeAndColor {
|
export class DashTypeAndColor {
|
||||||
constructor(
|
constructor(
|
||||||
public color: string,
|
public color: string,
|
||||||
public pointType: number) {}
|
public pointType: number,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FilterDefaults {
|
export class FilterDefaults {
|
||||||
constructor(
|
constructor(
|
||||||
public groupBy: Array<string>,
|
public groupBy: Array<string>,
|
||||||
public fields: Array<string>,
|
public fields: Array<string>,
|
||||||
public splitBy: string){}
|
public splitBy: string,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ResultMode {
|
export enum ResultMode {
|
||||||
CUT_AT_DOT = "CUT_AT_DOT",
|
CUT_AT_DOT = "CUT_AT_DOT",
|
||||||
FULL_VALUES = "FULL_VALUES"
|
FULL_VALUES = "FULL_VALUES",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('QueryAutocompleteComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ QueryAutocompleteComponent ]
|
imports: [QueryAutocompleteComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
|
import { Component, OnInit, Input, ViewChild, AfterViewInit } from '@angular/core';
|
||||||
import {FormControl, UntypedFormControl} from '@angular/forms';
|
import { FormControl, UntypedFormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {startWith, map} from 'rxjs/operators';
|
import {startWith, map} from 'rxjs/operators';
|
||||||
import {MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
import { MatAutocompleteTrigger, MatAutocomplete } from '@angular/material/autocomplete';
|
||||||
import { PlotService, PlotType, AutocompleteResult, Suggestion, ResultMode } from '../plot.service';
|
import { PlotService, PlotType, AutocompleteResult, Suggestion, ResultMode } from '../plot.service';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
import { NgFor, AsyncPipe } from '@angular/common';
|
||||||
|
import { MatOption } from '@angular/material/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-query-autocomplete',
|
selector: 'pdb-query-autocomplete',
|
||||||
templateUrl: './query-autocomplete.component.html',
|
templateUrl: './query-autocomplete.component.html',
|
||||||
styleUrls: ['./query-autocomplete.component.scss']
|
styleUrls: ['./query-autocomplete.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatInput, FormsModule, MatAutocompleteTrigger, ReactiveFormsModule, MatAutocomplete, NgFor, MatOption, AsyncPipe]
|
||||||
})
|
})
|
||||||
export class QueryAutocompleteComponent implements OnInit {
|
export class QueryAutocompleteComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('UploadPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ UploadPageComponent ]
|
imports: [UploadPageComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-upload-page',
|
selector: 'pdb-upload-page',
|
||||||
templateUrl: './upload-page.component.html',
|
templateUrl: './upload-page.component.html',
|
||||||
styleUrls: ['./upload-page.component.scss']
|
styleUrls: ['./upload-page.component.scss'],
|
||||||
|
standalone: true
|
||||||
})
|
})
|
||||||
export class UploadPageComponent implements OnInit {
|
export class UploadPageComponent implements OnInit {
|
||||||
|
|
||||||
|
|||||||
@@ -1,113 +1,155 @@
|
|||||||
<div id="visualization">
|
<div id="visualization">
|
||||||
<div id="query-box">
|
<div id="query-box">
|
||||||
<pdb-query-autocomplete #query></pdb-query-autocomplete>
|
<pdb-query-autocomplete #query></pdb-query-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="date-box">
|
<div id="date-box">
|
||||||
|
<app-date-picker #datePicker></app-date-picker>
|
||||||
|
<!--
|
||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Date Range:</mat-label>
|
<mat-label>Date Range:</mat-label>
|
||||||
<input matInput id="search-date-range" value="dateRange" name="dates" />
|
<input matInput id="search-date-range" value="dateRange" name="dates" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="filters">
|
<div id="filters">
|
||||||
<div id="filterpanel">
|
<div id="filterpanel">
|
||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Type:</mat-label>
|
<mat-label>Type:</mat-label>
|
||||||
<mat-select multiple [(ngModel)]="selectedPlotType" (ngModelChange)="changePlotType($event)">
|
<mat-select
|
||||||
<mat-option *ngFor="let plotType of plotTypes" [value]="plotType" [disabled]="!plotType.active">
|
multiple
|
||||||
<img src="assets/img/{{plotType.icon}}.svg" class="icon-select" /> {{plotType.name}}
|
[(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-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="pdb-form-full-width">
|
<mat-form-field class="pdb-form-full-width">
|
||||||
<mat-label>Group By:</mat-label>
|
<mat-label>Group By:</mat-label>
|
||||||
<mat-select multiple [(value)]="groupBy">
|
<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-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<pdb-limit-by #limitbycomponent></pdb-limit-by>
|
<pdb-limit-by #limitbycomponent></pdb-limit-by>
|
||||||
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
||||||
<mat-form-field >
|
<mat-form-field>
|
||||||
<mat-label>Intervals (only bar chart):</mat-label>
|
<mat-label>Intervals (only bar chart):</mat-label>
|
||||||
<mat-select [(value)]="intervalUnit">
|
<mat-select [(value)]="intervalUnit">
|
||||||
<mat-option value="NO_INTERVAL">-</mat-option>
|
<mat-option value="NO_INTERVAL">-</mat-option>
|
||||||
<mat-option value="SECOND">second</mat-option>
|
<mat-option value="SECOND">second</mat-option>
|
||||||
<mat-option value="MINUTE">minute</mat-option>
|
<mat-option value="MINUTE">minute</mat-option>
|
||||||
<mat-option value="HOUR">hour</mat-option>
|
<mat-option value="HOUR">hour</mat-option>
|
||||||
<mat-option value="DAY">day</mat-option>
|
<mat-option value="DAY">day</mat-option>
|
||||||
<mat-option value="WEEK">week</mat-option>
|
<mat-option value="WEEK">week</mat-option>
|
||||||
<mat-option value="MONTH">month</mat-option>
|
<mat-option value="MONTH">month</mat-option>
|
||||||
<mat-option value="YEAR">year</mat-option>
|
<mat-option value="YEAR">year</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div [hidden]="!selectedPlotTypesContains(['BAR', 'BOX'])">
|
<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>
|
</div>
|
||||||
<pdb-y-axis-definition #y1AxisDefinitionComponent yIndex="1"></pdb-y-axis-definition>
|
<pdb-y-axis-definition
|
||||||
<pdb-y-axis-definition #y2AxisDefinitionComponent yIndex="2" [hidden]="!y2AxisAvailable"></pdb-y-axis-definition>
|
#y1AxisDefinitionComponent
|
||||||
|
yIndex="1"
|
||||||
<mat-checkbox *ngIf="galleryEnabled" [(ngModel)]="enableGallery" (click)="toggleGallery($event)">Gallery</mat-checkbox>
|
></pdb-y-axis-definition>
|
||||||
|
<pdb-y-axis-definition
|
||||||
<mat-form-field *ngIf="enableGallery" class="pdb-form-full-width">
|
#y2AxisDefinitionComponent
|
||||||
|
yIndex="2"
|
||||||
|
[hidden]="!y2AxisAvailable"
|
||||||
|
></pdb-y-axis-definition>
|
||||||
|
|
||||||
|
<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-label>Split By:</mat-label>
|
||||||
<mat-select [(value)]="splitBy">
|
<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-select>
|
||||||
<mat-error *ngIf="splitBy == null || true">
|
<mat-error *ngIf="splitBy == null || true">
|
||||||
Please select a value!
|
Please select a value!
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
|
||||||
<div id="plot-button-bar">
|
<div id="plot-button-bar">
|
||||||
<a
|
<a
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
[routerLink]="['/vis']"
|
[routerLink]="['/vis']"
|
||||||
[queryParams]="{config: serializedConfig()}"
|
[queryParams]="{ config: serializedConfig() }"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
aria-label="open new window with the same search"
|
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"
|
||||||
<button
|
><img src="assets/img/link.svg" aria-hidden="true"
|
||||||
*ngIf="!enableGallery && !plotJobActive"
|
/></a>
|
||||||
[disabled]="plotJobActive"
|
<button
|
||||||
mat-button
|
*ngIf="!enableGallery && !plotJobActive"
|
||||||
matTooltip="Create Plot"
|
[disabled]="plotJobActive"
|
||||||
(click)="plot()">
|
mat-button
|
||||||
<img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" />
|
matTooltip="Create Plot"
|
||||||
|
(click)="plot()"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="assets/img/scatter-chart2.svg"
|
||||||
|
class="icon-inline"
|
||||||
|
aria-hidden="true"
|
||||||
|
title="create plot"
|
||||||
|
/>
|
||||||
Plot
|
Plot
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="enableGallery && !plotJobActive"
|
*ngIf="enableGallery && !plotJobActive"
|
||||||
mat-button
|
mat-button
|
||||||
matTooltip="Create Gallery"
|
matTooltip="Create Gallery"
|
||||||
(click)="gallery()"
|
(click)="gallery()"
|
||||||
[disabled]="this.splitBy == null">
|
[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)" />
|
>
|
||||||
|
<img
|
||||||
|
src="assets/img/four-squares-line.svg"
|
||||||
|
class="icon-inline"
|
||||||
|
aria-hidden="true"
|
||||||
|
title="Create Gallery (only active if 'Split' is set)"
|
||||||
|
/>
|
||||||
Gallery
|
Gallery
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="plotJobActive"
|
*ngIf="plotJobActive"
|
||||||
mat-button
|
mat-button
|
||||||
(click)="abort()"
|
(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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="results">
|
<div id="results">
|
||||||
<pdb-plot-view
|
<pdb-plot-view
|
||||||
#plotView
|
#plotView
|
||||||
(loadingEvent)="loading($event)"
|
(loadingEvent)="loading($event)"
|
||||||
(dateRangeUpdateEvent)="updateDateRange($event)"></pdb-plot-view>
|
(dateRangeUpdateEvent)="updateDateRange($event)"
|
||||||
<pdb-gallery-view
|
></pdb-plot-view>
|
||||||
#galleryView>
|
<pdb-gallery-view #galleryView> </pdb-gallery-view>
|
||||||
</pdb-gallery-view>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
grid:
|
grid:
|
||||||
"query-box query-box date-box" auto
|
"query-box query-box date-box" auto
|
||||||
"filters results results" 1fr
|
"filters results results" 1fr
|
||||||
/ 25.5em 3fr 23.5em;
|
/ 25.5em 3fr auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +43,8 @@
|
|||||||
|
|
||||||
#date-box{
|
#date-box{
|
||||||
grid-area: date-box;
|
grid-area: date-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('VisualizationPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ VisualizationPageComponent ]
|
imports: [VisualizationPageComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,76 @@
|
|||||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
import {
|
||||||
import { PlotService, PlotType, PlotRequest, TagField, FilterDefaults, DataType, AxesTypes, PlotConfig, RenderOptions, RenderOptionsMap, Suggestion } from '../plot.service';
|
AfterViewInit,
|
||||||
import { UntypedFormControl, } from '@angular/forms';
|
Component,
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
Input,
|
||||||
import { LimitByComponent } from '../limit-by/limit-by.component';
|
OnInit,
|
||||||
import { YAxisDefinitionComponent } from '../y-axis-definition/y-axis-definition.component';
|
ViewChild,
|
||||||
import { QueryAutocompleteComponent } from '../query-autocomplete/query-autocomplete.component';
|
} from "@angular/core";
|
||||||
import { PlotViewComponent, LoadingEvent } from '../plot-view/plot-view.component';
|
import {
|
||||||
import { GalleryViewComponent } from '../gallery-view/gallery-view.component';
|
AxesTypes,
|
||||||
import { WidgetDimensions } from '../dashboard.service';
|
DataType,
|
||||||
|
FilterDefaults,
|
||||||
|
PlotConfig,
|
||||||
|
PlotRequest,
|
||||||
|
PlotService,
|
||||||
|
PlotType,
|
||||||
|
RenderOptions,
|
||||||
|
RenderOptionsMap,
|
||||||
|
Suggestion,
|
||||||
|
TagField,
|
||||||
|
} from "../plot.service";
|
||||||
|
import { UntypedFormControl, FormsModule } 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";
|
||||||
|
import { MatFormField, MatLabel, MatError } from "@angular/material/form-field";
|
||||||
|
import { MatSelect } from "@angular/material/select";
|
||||||
|
import { NgFor, NgIf } from "@angular/common";
|
||||||
|
import { MatOption } from "@angular/material/core";
|
||||||
|
import { MatCheckbox } from "@angular/material/checkbox";
|
||||||
|
import { MatIconAnchor, MatButton } from "@angular/material/button";
|
||||||
|
import { RouterLink } from "@angular/router";
|
||||||
|
import { MatTooltip } from "@angular/material/tooltip";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-visualization-page',
|
selector: "pdb-visualization-page",
|
||||||
templateUrl: './visualization-page.component.html',
|
templateUrl: "./visualization-page.component.html",
|
||||||
styleUrls: ['./visualization-page.component.scss']
|
styleUrls: ["./visualization-page.component.scss"],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
QueryAutocompleteComponent,
|
||||||
|
DatePickerComponent,
|
||||||
|
MatFormField,
|
||||||
|
MatLabel,
|
||||||
|
MatSelect,
|
||||||
|
FormsModule,
|
||||||
|
NgFor,
|
||||||
|
MatOption,
|
||||||
|
LimitByComponent,
|
||||||
|
MatCheckbox,
|
||||||
|
YAxisDefinitionComponent,
|
||||||
|
NgIf,
|
||||||
|
MatError,
|
||||||
|
MatIconAnchor,
|
||||||
|
RouterLink,
|
||||||
|
MatButton,
|
||||||
|
MatTooltip,
|
||||||
|
PlotViewComponent,
|
||||||
|
GalleryViewComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
readonly DATE_PATTERN = "YYYY-MM-DD HH:mm:ss"; // for moment-JS
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
@@ -23,47 +78,50 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
galleryEnabled = true;
|
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>();
|
selectedPlotType = new Array<PlotType>();
|
||||||
plotTypes: PlotType[] = [];
|
plotTypes: PlotType[] = [];
|
||||||
|
|
||||||
tagFields: Array<TagField> = new Array<TagField>();
|
tagFields: Array<TagField> = new Array<TagField>();
|
||||||
|
|
||||||
groupBy = new Array<TagField>();
|
groupBy = new Array<TagField>();
|
||||||
|
|
||||||
@ViewChild('limitbycomponent')
|
@ViewChild("limitbycomponent")
|
||||||
private limitbycomponent! : LimitByComponent;
|
private limitbycomponent!: LimitByComponent;
|
||||||
|
|
||||||
|
@ViewChild("y1AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||||
@ViewChild('y1AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
private y1AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||||
private y1AxisDefinitionComponent! : YAxisDefinitionComponent;
|
|
||||||
|
@ViewChild("y2AxisDefinitionComponent", { read: YAxisDefinitionComponent })
|
||||||
@ViewChild('y2AxisDefinitionComponent', { read: YAxisDefinitionComponent })
|
private y2AxisDefinitionComponent!: YAxisDefinitionComponent;
|
||||||
private y2AxisDefinitionComponent! : YAxisDefinitionComponent;
|
|
||||||
|
@ViewChild("query")
|
||||||
@ViewChild('query')
|
|
||||||
query!: QueryAutocompleteComponent;
|
query!: QueryAutocompleteComponent;
|
||||||
|
|
||||||
@ViewChild('plotView')
|
@ViewChild("plotView")
|
||||||
plotView!: PlotViewComponent;
|
plotView!: PlotViewComponent;
|
||||||
|
|
||||||
@ViewChild('galleryView')
|
@ViewChild("galleryView")
|
||||||
galleryView!: GalleryViewComponent;
|
galleryView!: GalleryViewComponent;
|
||||||
|
|
||||||
|
@ViewChild("datePicker")
|
||||||
|
datePicker!: DatePickerComponent;
|
||||||
|
|
||||||
enableGallery = false;
|
enableGallery = false;
|
||||||
splitBy : TagField | undefined = undefined;
|
splitBy: TagField | undefined = undefined;
|
||||||
y2AxisAvailable = false;
|
y2AxisAvailable = false;
|
||||||
|
|
||||||
intervalUnit = 'NO_INTERVAL';
|
intervalUnit = "NO_INTERVAL";
|
||||||
intervalValue = 1;
|
intervalValue = 1;
|
||||||
renderBarChartTickLabels = false;
|
renderBarChartTickLabels = false;
|
||||||
|
|
||||||
plotJobActive = false;
|
plotJobActive = false;
|
||||||
|
|
||||||
constructor(private plotService: PlotService, private snackBar: MatSnackBar) {
|
constructor(private plotService: PlotService, private snackBar: MatSnackBar) {
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
if (!this.defaultConfig && params.get("config")) {
|
if (!this.defaultConfig && params.get("config")) {
|
||||||
const config = JSON.parse(params.get("config")!);
|
const config = JSON.parse(params.get("config")!);
|
||||||
@@ -71,118 +129,126 @@ export class VisualizationPageComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(message:string) {
|
showError(message: string) {
|
||||||
this.snackBar.open(message, "", {
|
this.snackBar.open(message, "", {
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
verticalPosition: 'top'
|
verticalPosition: "top",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
(<any>window).initDatePicker();
|
|
||||||
|
|
||||||
this.plotTypes = this.plotService.getPlotTypes();
|
this.plotTypes = this.plotService.getPlotTypes();
|
||||||
this.selectedPlotType.push(this.plotTypes[0]);
|
this.selectedPlotType.push(this.plotTypes[0]);
|
||||||
|
|
||||||
this.plotService.getFilterDefaults().subscribe((filterDefaults: FilterDefaults) => {
|
|
||||||
|
|
||||||
filterDefaults.fields.forEach((name:string) => {
|
|
||||||
this.tagFields.push(new TagField(name));
|
|
||||||
},
|
|
||||||
(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);
|
|
||||||
|
|
||||||
if (this.defaultConfig) {
|
this.plotService.getFilterDefaults().subscribe(
|
||||||
this.plot();
|
(filterDefaults: FilterDefaults) => {
|
||||||
}
|
filterDefaults.fields.forEach((name: string) => {
|
||||||
});
|
this.tagFields.push(new TagField(name));
|
||||||
|
}, (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
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.defaultConfig) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (this.defaultConfig) {
|
if (this.defaultConfig) {
|
||||||
const c = this.defaultConfig;
|
const c = this.defaultConfig;
|
||||||
this.query.suggestionFetcherEnabled = false;
|
this.query.suggestionFetcherEnabled = false;
|
||||||
this.query.queryField.setValue(new Suggestion(c.query, c.query, c.query.length));
|
this.query.queryField.setValue(
|
||||||
this.query.suggestionFetcherEnabled = true;
|
new Suggestion(c.query, c.query, c.query.length),
|
||||||
|
);
|
||||||
this.selectedPlotType = this.plotTypes.filter(pt => c.aggregates.includes(pt.id));
|
this.query.suggestionFetcherEnabled = true;
|
||||||
this.changePlotType(this.selectedPlotType);
|
|
||||||
this.updateDateRange(c.dateRange, false);
|
|
||||||
this.limitbycomponent.limitBy = c.limitBy;
|
|
||||||
this.limitbycomponent.limit = c.limit;
|
|
||||||
this.intervalUnit = c.intervalUnit;
|
|
||||||
this.intervalValue = c.intervalValue;
|
|
||||||
this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale;
|
|
||||||
this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin;
|
|
||||||
this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax;
|
|
||||||
this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit;
|
|
||||||
|
|
||||||
if (c.y2) {
|
this.selectedPlotType = this.plotTypes.filter((pt) =>
|
||||||
this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale;
|
c.aggregates.includes(pt.id)
|
||||||
this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin;
|
);
|
||||||
this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax;
|
this.changePlotType(this.selectedPlotType);
|
||||||
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
this.updateDateRange(c.dateRange, false);
|
||||||
|
this.limitbycomponent.limitBy = c.limitBy;
|
||||||
|
this.limitbycomponent.limit = c.limit;
|
||||||
|
this.intervalUnit = c.intervalUnit;
|
||||||
|
this.intervalValue = c.intervalValue;
|
||||||
|
this.y1AxisDefinitionComponent.yAxisScale = c.y1.axisScale;
|
||||||
|
this.y1AxisDefinitionComponent.minYValue = c.y1.rangeMin;
|
||||||
|
this.y1AxisDefinitionComponent.maxYValue = c.y1.rangeMax;
|
||||||
|
this.y1AxisDefinitionComponent.yAxisUnit = c.y1.rangeUnit;
|
||||||
|
|
||||||
|
if (c.y2) {
|
||||||
|
this.y2AxisDefinitionComponent.yAxisScale = c.y2.axisScale;
|
||||||
|
this.y2AxisDefinitionComponent.minYValue = c.y2.rangeMin;
|
||||||
|
this.y2AxisDefinitionComponent.maxYValue = c.y2.rangeMax;
|
||||||
|
this.y2AxisDefinitionComponent.yAxisUnit = c.y2.rangeUnit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
toggleGallery(event: Event){
|
toggleGallery(event: Event) {
|
||||||
this.galleryView.show = this.enableGallery;
|
this.galleryView.show = this.enableGallery;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading(event: LoadingEvent) {
|
loading(event: LoadingEvent) {
|
||||||
this.plotJobActive = event.loading;
|
this.plotJobActive = event.loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDateRange(newDateRange: string, updatePlot=true) {
|
updateDateRange(newDateRange: DateValue, updatePlot = true) {
|
||||||
(<HTMLInputElement>document.getElementById("search-date-range")).value = newDateRange;
|
this.datePicker.setDateValue(newDateRange);
|
||||||
if (updatePlot){
|
if (updatePlot) {
|
||||||
this.plot();
|
this.plot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changePlotType(selectedPlotTypes: Array<PlotType>) {
|
changePlotType(selectedPlotTypes: Array<PlotType>) {
|
||||||
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(selectedPlotTypes));
|
const compatiblePlotTypes = this.plotTypes.filter((pt) =>
|
||||||
this.plotTypes.forEach(pt => pt.active=false);
|
pt.compatible(selectedPlotTypes)
|
||||||
compatiblePlotTypes.forEach(pt => pt.active=true);
|
);
|
||||||
|
this.plotTypes.forEach((pt) => pt.active = false);
|
||||||
|
compatiblePlotTypes.forEach((pt) => pt.active = true);
|
||||||
|
|
||||||
const axesTypes = this.getAxes();
|
const axesTypes = this.getAxes();
|
||||||
this.y2AxisAvailable = axesTypes.y.length == 2;
|
this.y2AxisAvailable = axesTypes.y.length == 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedPlotTypesContains(plotTypeIds: Array<string>){
|
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(): DateValue {
|
||||||
dateRangeAsString() : string {
|
return this.datePicker.getDateValue();
|
||||||
return (<HTMLInputElement>document.getElementById("search-date-range")).value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery(){
|
gallery() {
|
||||||
if (this.splitBy != null){
|
if (this.splitBy != null) {
|
||||||
this.plotView.imageUrl = '';
|
this.plotView.imageUrl = "";
|
||||||
this.plotView.stats = null;
|
this.plotView.stats = null;
|
||||||
this.galleryView.show=true;
|
this.galleryView.show = true;
|
||||||
const request = this.createPlotRequest();
|
const request = this.createPlotRequest();
|
||||||
this.galleryView.renderGallery(request, this.splitBy.name);
|
this.galleryView.renderGallery(request, this.splitBy.name);
|
||||||
} else {
|
} else {
|
||||||
console.error("variable splitBy was null when rendering gallery");
|
console.error("variable splitBy was null when rendering gallery");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAxes() : AxesTypes {
|
getAxes(): AxesTypes {
|
||||||
|
|
||||||
const x = new Array<DataType>();
|
const x = new Array<DataType>();
|
||||||
const y = new Array<DataType>();
|
const y = new Array<DataType>();
|
||||||
|
|
||||||
for(var i = 0; i < this.selectedPlotType.length; i++){
|
for (var i = 0; i < this.selectedPlotType.length; i++) {
|
||||||
var plotType = this.selectedPlotType[i];
|
var plotType = this.selectedPlotType[i];
|
||||||
if (!x.includes(plotType.xAxis)) {
|
if (!x.includes(plotType.xAxis)) {
|
||||||
x.push(plotType.xAxis);
|
x.push(plotType.xAxis);
|
||||||
@@ -191,75 +257,83 @@ toggleGallery(event: Event){
|
|||||||
y.push(plotType.yAxis);
|
y.push(plotType.yAxis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AxesTypes(x,y);
|
return new AxesTypes(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
this.plotService.abort((<any>window).submitterId).subscribe({
|
this.plotService.abort((<any> window).submitterId).subscribe({
|
||||||
complete: () => {
|
complete: () => {
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
plot(){
|
plot() {
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
this.plotView.plot(config, this.plotDimensionSupplier);
|
this.plotView.plot(config, this.plotDimensionSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
plotDimensionSupplier(): WidgetDimensions{
|
plotDimensionSupplier(): WidgetDimensions {
|
||||||
const results = document.getElementById("results");
|
const results = document.getElementById("results");
|
||||||
return new WidgetDimensions(
|
return new WidgetDimensions(
|
||||||
results != null ? results.offsetWidth-1 : 1024,
|
results != null ? results.offsetWidth - 1 : 1024,
|
||||||
results != null ? results.offsetHeight-1: 1024);
|
results != null ? results.offsetHeight - 1 : 1024,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlotConfig(): PlotConfig {
|
createPlotConfig(): PlotConfig {
|
||||||
const aggregates = new Array<string>();
|
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 y1 = this.y1AxisDefinitionComponent.getAxisDefinition();
|
||||||
const y2 = this.y2AxisDefinitionComponent ? this.y2AxisDefinitionComponent.getAxisDefinition() : undefined;
|
const y2 = this.y2AxisDefinitionComponent
|
||||||
|
? this.y2AxisDefinitionComponent.getAxisDefinition()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const config = new PlotConfig(
|
const config = new PlotConfig(
|
||||||
this.query.query,
|
this.query.query,
|
||||||
this.groupBy.map(o => o.name),
|
this.groupBy.map((o) => o.name),
|
||||||
this.limitbycomponent.limitBy,
|
this.limitbycomponent.limitBy,
|
||||||
this.limitbycomponent.limit,
|
this.limitbycomponent.limit,
|
||||||
y1,
|
y1,
|
||||||
y2,
|
y2,
|
||||||
this.dateRangeAsString(), // dateRange
|
this.datePicker.getDateValue(), // dateRange
|
||||||
aggregates, // aggregates
|
aggregates, // aggregates
|
||||||
this.intervalUnit,
|
this.intervalUnit,
|
||||||
this.intervalValue,
|
this.intervalValue,
|
||||||
this.renderBarChartTickLabels,
|
this.renderBarChartTickLabels,
|
||||||
);
|
);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
createPlotRequest(): PlotRequest {
|
createPlotRequest(): PlotRequest {
|
||||||
const results = document.getElementById("results");
|
const results = document.getElementById("results");
|
||||||
|
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
|
|
||||||
const renderOptions : RenderOptionsMap = {
|
const renderOptions: RenderOptionsMap = {
|
||||||
'main': new RenderOptions(results!.offsetHeight-1, results!.offsetWidth-1, false, true),
|
"main": new RenderOptions(
|
||||||
'thumbnail': new RenderOptions(200, 300, false, false),
|
results!.offsetHeight - 1,
|
||||||
|
results!.offsetWidth - 1,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
"thumbnail": new RenderOptions(200, 300, false, false),
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = new PlotRequest(
|
const request = new PlotRequest(
|
||||||
(<any>window).submitterId,
|
(<any> window).submitterId,
|
||||||
config,
|
config,
|
||||||
renderOptions
|
renderOptions,
|
||||||
);
|
);
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedConfig(): string {
|
serializedConfig(): string {
|
||||||
try{
|
try {
|
||||||
const config = this.createPlotConfig();
|
const config = this.createPlotConfig();
|
||||||
return JSON.stringify(config);
|
return JSON.stringify(config);
|
||||||
}catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ describe('YAxisDefinitionComponent', () => {
|
|||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ YAxisDefinitionComponent ]
|
imports: [YAxisDefinitionComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { YAxisDefinition } from '../plot.service';
|
import { YAxisDefinition } from '../plot.service';
|
||||||
|
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||||
|
import { MatSelect } from '@angular/material/select';
|
||||||
|
import { MatOption, MatOptgroup } from '@angular/material/core';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { MatInput } from '@angular/material/input';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pdb-y-axis-definition',
|
selector: 'pdb-y-axis-definition',
|
||||||
templateUrl: './y-axis-definition.component.html',
|
templateUrl: './y-axis-definition.component.html',
|
||||||
styleUrls: ['./y-axis-definition.component.scss']
|
styleUrls: ['./y-axis-definition.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatFormField, MatLabel, MatSelect, MatOption, MatOptgroup, NgIf, MatInput, FormsModule]
|
||||||
})
|
})
|
||||||
export class YAxisDefinitionComponent {
|
export class YAxisDefinitionComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -1,410 +0,0 @@
|
|||||||
.daterangepicker {
|
|
||||||
position: absolute;
|
|
||||||
color: inherit;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
width: 278px;
|
|
||||||
max-width: none;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 7px;
|
|
||||||
top: 100px;
|
|
||||||
left: 20px;
|
|
||||||
z-index: 3001;
|
|
||||||
display: none;
|
|
||||||
font-family: arial;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker:before, .daterangepicker:after {
|
|
||||||
position: absolute;
|
|
||||||
display: inline-block;
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker:before {
|
|
||||||
top: -7px;
|
|
||||||
border-right: 7px solid transparent;
|
|
||||||
border-left: 7px solid transparent;
|
|
||||||
border-bottom: 7px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker:after {
|
|
||||||
top: -6px;
|
|
||||||
border-right: 6px solid transparent;
|
|
||||||
border-bottom: 6px solid #fff;
|
|
||||||
border-left: 6px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.opensleft:before {
|
|
||||||
right: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.opensleft:after {
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.openscenter:before {
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.openscenter:after {
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 0;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.opensright:before {
|
|
||||||
left: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.opensright:after {
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.drop-up {
|
|
||||||
margin-top: -7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.drop-up:before {
|
|
||||||
top: initial;
|
|
||||||
bottom: -7px;
|
|
||||||
border-bottom: initial;
|
|
||||||
border-top: 7px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.drop-up:after {
|
|
||||||
top: initial;
|
|
||||||
bottom: -6px;
|
|
||||||
border-bottom: initial;
|
|
||||||
border-top: 6px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.single .drp-selected {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-calendar .drp-calendar {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-calendar .drp-buttons {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.auto-apply .drp-buttons {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar {
|
|
||||||
display: none;
|
|
||||||
max-width: 270px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.left {
|
|
||||||
padding: 8px 0 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.right {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.single .calendar-table {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {
|
|
||||||
color: #fff;
|
|
||||||
border: solid black;
|
|
||||||
border-width: 0 2px 2px 0;
|
|
||||||
border-radius: 0;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table .next span {
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
-webkit-transform: rotate(-45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table .prev span {
|
|
||||||
transform: rotate(135deg);
|
|
||||||
-webkit-transform: rotate(135deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {
|
|
||||||
white-space: nowrap;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
min-width: 32px;
|
|
||||||
width: 32px;
|
|
||||||
height: 24px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
white-space: nowrap;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table {
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-table table {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
border-spacing: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
|
|
||||||
background-color: #eee;
|
|
||||||
border-color: transparent;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.week, .daterangepicker th.week {
|
|
||||||
font-size: 80%;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: transparent;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.in-range {
|
|
||||||
background-color: #ebf4f8;
|
|
||||||
border-color: transparent;
|
|
||||||
color: #000;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.start-date {
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.end-date {
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.start-date.end-date {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.active, .daterangepicker td.active:hover {
|
|
||||||
background-color: #357ebd;
|
|
||||||
border-color: transparent;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker th.month {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker td.disabled, .daterangepicker option.disabled {
|
|
||||||
color: #999;
|
|
||||||
cursor: not-allowed;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker select.monthselect, .daterangepicker select.yearselect {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 1px;
|
|
||||||
height: auto;
|
|
||||||
margin: 0;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker select.monthselect {
|
|
||||||
margin-right: 2%;
|
|
||||||
width: 56%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker select.yearselect {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
|
|
||||||
width: 50px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #eee;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
padding: 2px;
|
|
||||||
outline: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-time {
|
|
||||||
text-align: center;
|
|
||||||
margin: 4px auto 0 auto;
|
|
||||||
line-height: 30px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .calendar-time select.disabled {
|
|
||||||
color: #ccc;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-buttons {
|
|
||||||
clear: both;
|
|
||||||
text-align: right;
|
|
||||||
padding: 8px;
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
display: none;
|
|
||||||
line-height: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-selected {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 12px;
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-buttons .btn {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-ranges.single.rtl .drp-calendar.left {
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-ranges.single.ltr .drp-calendar.left {
|
|
||||||
border-left: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-ranges.rtl .drp-calendar.right {
|
|
||||||
border-right: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-ranges.ltr .drp-calendar.left {
|
|
||||||
border-left: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges {
|
|
||||||
float: none;
|
|
||||||
text-align: left;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.show-calendar .ranges {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges li {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges li:hover {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges li.active {
|
|
||||||
background-color: #08c;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Larger Screen Styling */
|
|
||||||
@media (min-width: 564px) {
|
|
||||||
.daterangepicker {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges ul {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.single .ranges ul {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.single .drp-calendar.left {
|
|
||||||
clear: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.single .ranges, .daterangepicker.single .drp-calendar {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker {
|
|
||||||
direction: ltr;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.left {
|
|
||||||
clear: left;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.left .calendar-table {
|
|
||||||
border-right: none;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.right {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.right .calendar-table {
|
|
||||||
border-left: none;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.left .calendar-table {
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges, .daterangepicker .drp-calendar {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 730px) {
|
|
||||||
.daterangepicker .ranges {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .ranges {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker.rtl .ranges {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.daterangepicker .drp-calendar.left {
|
|
||||||
clear: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
2
pdb-js/src/assets/js/moment-2.29.1.min.js
vendored
2
pdb-js/src/assets/js/moment-2.29.1.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -8,45 +8,13 @@
|
|||||||
<link rel="icon" type="image/x-icon" href="/assets/img/favicon.png">
|
<link rel="icon" type="image/x-icon" href="/assets/img/favicon.png">
|
||||||
|
|
||||||
<script type="text/javascript" src="/assets/js/jquery-3.6.0.min.js"></script>
|
<script type="text/javascript" src="/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
<script type="text/javascript" src="/assets/js/moment-2.29.1.min.js"></script>
|
|
||||||
<script type="text/javascript" src="/assets/js/daterangepicker-3.1.js"></script>
|
|
||||||
<script type="text/javascript" src="/assets/js/invaders.js"></script>
|
<script type="text/javascript" src="/assets/js/invaders.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/css/daterangepicker-3.1.css" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/css/invaders.css" />
|
<link rel="stylesheet" type="text/css" href="/assets/css/invaders.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
function initDatePicker() {
|
|
||||||
$('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
|
|
||||||
},
|
|
||||||
ranges: {
|
|
||||||
'Today': [moment().startOf('day'), moment().endOf('day')],
|
|
||||||
'Yesterday': [moment().subtract(1, 'days').startOf('day'), moment().subtract(1, 'days').endOf('day')],
|
|
||||||
'Last 7 Days': [moment().subtract(6, 'days').startOf('day'), moment().endOf('day')],
|
|
||||||
'This Week': [moment().startOf('week'), moment().endOf('day')],
|
|
||||||
'Last Week': [moment().subtract(7, 'days').startOf('week'), moment().subtract(7, 'days').endOf('week')],
|
|
||||||
'Last 30 Days': [moment().subtract(29, 'days').startOf('day'), moment().endOf('day')],
|
|
||||||
'This Month': [moment().startOf('month'), moment().endOf('month').endOf('day')],
|
|
||||||
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
|
|
||||||
'Last 3 Months': [moment().subtract(3, 'month').startOf('month'),moment().subtract(1, 'month').endOf('month').endOf('day')],
|
|
||||||
'This Year': [moment().startOf('year'),moment().endOf('month').endOf('day')],
|
|
||||||
'Last Year': [moment().subtract(1, 'year').startOf('year'),moment().subtract(1, 'year').endOf('year')],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$( document ).ready(function() {
|
$( document ).ready(function() {
|
||||||
initInvaders('results');
|
initInvaders('results');
|
||||||
document.addEventListener("invadersPause", function(event) {
|
document.addEventListener("invadersPause", function(event) {
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode, importProvidersFrom } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
|
||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
|
import { MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
|
||||||
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
|
import { MarkdownModule } from 'ngx-markdown';
|
||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
import { provideRouter, Routes, withDebugTracing } from '@angular/router';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
@@ -14,5 +20,29 @@ if (environment.production) {
|
|||||||
|
|
||||||
(<any>window).submitterId = (<any>window).randomId();
|
(<any>window).submitterId = (<any>window).randomId();
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: "", loadComponent: () => import("./app/main-page/main-page.component").then( m => m.MainPageComponent) },
|
||||||
|
{ path: "vis", loadComponent: () => import("./app/visualization-page/visualization-page.component").then(m => m.VisualizationPageComponent) },
|
||||||
|
{ path: "dashboard", loadComponent: () => import("./app/dashboard-page/dashboard-page.component").then(m => m.DashboardPageComponent) },
|
||||||
|
{ path: "dashboard/:id", loadComponent: () => import("./app/dashboard-page/dashboard/dashboard.component").then(m => m.DashboardComponent) },
|
||||||
|
{ path: "upload", loadComponent: () => import("./app/upload-page/upload-page.component").then(m => m.UploadPageComponent) },
|
||||||
|
{ path: "grid", loadComponent: () => import("./app/customizable-grid/customizable-grid.component").then(m => m.CustomizableGridComponent) },
|
||||||
|
{ path: "help", loadComponent: () => import("./app/help-page/help-page.component").then(m => m.HelpPageComponent) },
|
||||||
|
];
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, {
|
||||||
|
providers: [
|
||||||
|
provideRouter(routes, withDebugTracing()),
|
||||||
|
importProvidersFrom(
|
||||||
|
MarkdownModule.forRoot(),
|
||||||
|
),
|
||||||
|
{
|
||||||
|
provide: MAT_DIALOG_DEFAULT_OPTIONS,
|
||||||
|
useValue: { hasBackdrop: true },
|
||||||
|
},
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideAnimations()
|
||||||
|
]
|
||||||
|
})
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
|
|||||||
@@ -14,25 +14,21 @@
|
|||||||
// If you don't need the default component typographies but still want the hierarchy styles,
|
// If you don't need the default component typographies but still want the hierarchy styles,
|
||||||
// you can delete this line and instead use:
|
// you can delete this line and instead use:
|
||||||
// `@include mat.legacy-typography-hierarchy(mat.define-typography-config());`
|
// `@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();
|
@include mat.all-component-typographies();
|
||||||
/* TODO(mdc-migration): Remove legacy-core once all legacy components are migrated*/
|
|
||||||
@include mat.legacy-core();
|
|
||||||
@include mat.core();
|
@include mat.core();
|
||||||
|
|
||||||
// Define the palettes for your theme using the Material Design palettes available in palette.scss
|
// 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
|
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
|
||||||
// hue. Available color palettes: https://material.io/design/color/
|
// hue. Available color palettes: https://material.io/design/color/
|
||||||
$candy-app-primary: mat.define-palette(mat.$blue-palette);
|
$candy-app-primary: mat.m2-define-palette(mat.$m2-blue-palette);
|
||||||
$candy-app-accent: mat.define-palette(mat.$blue-palette, A200, A100, A400);
|
$candy-app-accent: mat.m2-define-palette(mat.$m2-blue-palette, A200, A100, A400);
|
||||||
|
|
||||||
// The warn palette is optional (defaults to red).
|
// 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
|
// Create the theme object. A theme consists of configurations for individual
|
||||||
// theming systems such as "color" or "typography".
|
// theming systems such as "color" or "typography".
|
||||||
$candy-app-theme: mat.define-light-theme((
|
$candy-app-theme: mat.m2-define-light-theme((
|
||||||
color: (
|
color: (
|
||||||
primary: $candy-app-primary,
|
primary: $candy-app-primary,
|
||||||
accent: $candy-app-accent,
|
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.
|
// Include theme styles for core and each component used in your app.
|
||||||
// Alternatively, you can import and @include the theme mixins for each component
|
// Alternatively, you can import and @include the theme mixins for each component
|
||||||
// that you are using.
|
// 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);
|
@include mat.all-component-themes($candy-app-theme);
|
||||||
|
|
||||||
|
|
||||||
@@ -71,6 +65,8 @@ grey
|
|||||||
*/
|
*/
|
||||||
$background-color: #CBD7F4;
|
$background-color: #CBD7F4;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mat-mdc-option span.mdc-list-item__primary-text,
|
.mat-mdc-option span.mdc-list-item__primary-text,
|
||||||
.mdc-list-item__primary-text {
|
.mdc-list-item__primary-text {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -84,6 +80,12 @@ mat-form-field .mat-mdc-option span.mdc-list-item__primary-text{
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
// set colors for flat buttons (directive mat-flat-button)
|
||||||
|
// make them blue with white text when active and transparent with the default text color of light grey when disabled
|
||||||
|
--mdc-filled-button-label-text-color: white;
|
||||||
|
--mdc-filled-button-container-color: #005cbb;
|
||||||
|
--mdc-filled-button-disabled-container-color: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@@ -97,6 +99,10 @@ h2 {
|
|||||||
margin-block-end: 0.83rem;
|
margin-block-end: 0.83rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-inline {
|
.icon-inline {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
@@ -107,13 +113,6 @@ button[disabled] .icon-inline {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.save-button {
|
|
||||||
background-color: #ff9900;
|
|
||||||
}
|
|
||||||
button.save-button:disabled {
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tiny {
|
.icon-tiny {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
@@ -178,6 +177,9 @@ a.external-link:after {
|
|||||||
mat-form-field.pdb-form-full-width {
|
mat-form-field.pdb-form-full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
mat-form-field.pdb-form-number-small {
|
||||||
|
width: 4.5em;
|
||||||
|
}
|
||||||
mat-form-field.pdb-form-number {
|
mat-form-field.pdb-form-number {
|
||||||
width: 5.5em;
|
width: 5.5em;
|
||||||
}
|
}
|
||||||
@@ -197,7 +199,7 @@ app-add-text-dialog .mat-mdc-form-field-subscript-wrapper {
|
|||||||
|
|
||||||
.errorPanel {
|
.errorPanel {
|
||||||
padding: 1ex;
|
padding: 1ex;
|
||||||
background-color: map-get(mat.$red-palette, 100);
|
background-color: map-get(mat.$m2-red-palette, 100);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,4 +248,35 @@ markdown tfoot {
|
|||||||
|
|
||||||
markdown pre {
|
markdown pre {
|
||||||
font-family: monospace;
|
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;}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"outDir": "./dist/out-tsc",
|
"outDir": "./dist/out-tsc",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
package org.lucares.pdb.plot.api;
|
package org.lucares.pdb.plot.api;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.lucares.pdb.api.DateTimeRange;
|
import org.lucares.pdb.api.DateTimeRange;
|
||||||
import org.lucares.recommind.logs.GnuplotAxis;
|
import org.lucares.recommind.logs.GnuplotAxis;
|
||||||
import org.lucares.recommind.logs.GnuplotSettings;
|
import org.lucares.recommind.logs.GnuplotSettings;
|
||||||
import org.lucares.utils.Preconditions;
|
|
||||||
|
|
||||||
public class PlotSettings {
|
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;
|
private String query;
|
||||||
|
|
||||||
@@ -27,7 +23,7 @@ public class PlotSettings {
|
|||||||
|
|
||||||
private int limit;
|
private int limit;
|
||||||
|
|
||||||
private String dateRangeAsString;
|
private DateValue dateValue;
|
||||||
|
|
||||||
private YAxisDefinition y1;
|
private YAxisDefinition y1;
|
||||||
private YAxisDefinition y2;
|
private YAxisDefinition y2;
|
||||||
@@ -80,30 +76,32 @@ public class PlotSettings {
|
|||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDateRange() {
|
public DateValue getDateRange() {
|
||||||
return dateRangeAsString;
|
return dateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDateRange(final String dateRangeAsString) {
|
public void setDateRange(final DateValue dateValue) {
|
||||||
this.dateRangeAsString = dateRangeAsString;
|
this.dateValue = dateValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeRange dateRange() {
|
public DateTimeRange dateRange() {
|
||||||
|
|
||||||
final String[] startEnd = dateRangeAsString.split(Pattern.quote(" - "));
|
switch (this.dateValue.getType()) {
|
||||||
Preconditions.checkEqual(startEnd.length, 2, "invalid date range: ''{0}''", dateRangeAsString);
|
case RELATIVE:
|
||||||
|
case QUICK:
|
||||||
final OffsetDateTime startDate = LocalDateTime.parse(startEnd[0], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
final DateTimeRange dateTimeRange = DateTimeRangeParser.parse(OffsetDateTime.now(), dateValue.getValue());
|
||||||
final OffsetDateTime endDate = LocalDateTime.parse(startEnd[1], DATE_FORMAT).atOffset(ZoneOffset.UTC);
|
return dateTimeRange;
|
||||||
|
case ABSOLUTE:
|
||||||
return new DateTimeRange(startDate, endDate);
|
return DateTimeRangeParser.parseAbsolute(dateValue.getValue());
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PlotSettings [query=" + query + ", groupBy=" + groupBy + ", limitBy=" + limitBy + ", limit=" + limit
|
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 + "]";
|
+ ", renders=" + renders + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.lucares.pdb.plot.api;
|
|||||||
public class RenderOptions {
|
public class RenderOptions {
|
||||||
private int height;
|
private int height;
|
||||||
private int width;
|
private int width;
|
||||||
private boolean keyOutside;
|
private boolean showKey;
|
||||||
private boolean renderLabels;
|
private boolean renderLabels;
|
||||||
|
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
@@ -22,12 +22,12 @@ public class RenderOptions {
|
|||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKeyOutside() {
|
public boolean isShowKey() {
|
||||||
return keyOutside;
|
return showKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyOutside(final boolean keyOutside) {
|
public void setShowKey(final boolean showKey) {
|
||||||
this.keyOutside = keyOutside;
|
this.showKey = showKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRenderLabels() {
|
public boolean isRenderLabels() {
|
||||||
|
|||||||
@@ -50,18 +50,16 @@ public class GnuplotFileGenerator implements Appender {
|
|||||||
|
|
||||||
appendln(result, "set nokey");
|
appendln(result, "set nokey");
|
||||||
} else {
|
} else {
|
||||||
if (settings.isKeyOutside()) {
|
if (!settings.isShowKey()) {
|
||||||
appendfln(result, "set key outside");
|
appendfln(result, "set nokey");
|
||||||
} else {
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
appendln(result, "set lmargin 11"); // margin 11 -> 110px
|
|
||||||
appendln(result, "set rmargin 11"); // margin 11 -> 110px
|
|
||||||
appendln(result, "set tmargin 3"); // margin 3 -> 57px - marker (1)
|
|
||||||
appendln(result, "set bmargin 4"); // margin 4 -> 76
|
|
||||||
}
|
}
|
||||||
|
// 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)
|
||||||
|
appendln(result, "set lmargin 11"); // margin 11 -> 110px
|
||||||
|
appendln(result, "set rmargin 11"); // margin 11 -> 110px
|
||||||
|
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 xrange [-1:1]");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class GnuplotSettings {
|
|||||||
private YAxisDefinition y1;
|
private YAxisDefinition y1;
|
||||||
private YAxisDefinition y2;
|
private YAxisDefinition y2;
|
||||||
private AggregateHandlerCollection aggregates;
|
private AggregateHandlerCollection aggregates;
|
||||||
private boolean keyOutside = false;
|
private boolean showKey = false;
|
||||||
|
|
||||||
private AxisSettings xAxisSettings = new AxisSettings();
|
private AxisSettings xAxisSettings = new AxisSettings();
|
||||||
private boolean renderLabels = true;
|
private boolean renderLabels = true;
|
||||||
@@ -101,12 +101,12 @@ public class GnuplotSettings {
|
|||||||
return aggregates;
|
return aggregates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyOutside(final boolean keyOutside) {
|
public void setShowKey(final boolean showKey) {
|
||||||
this.keyOutside = keyOutside;
|
this.showKey = showKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKeyOutside() {
|
public boolean isShowKey() {
|
||||||
return keyOutside;
|
return showKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void renderLabels(final boolean renderLabels) {
|
public void renderLabels(final boolean renderLabels) {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class Plotter {
|
|||||||
gnuplotSettings.setY1(plotSettings.getY1());
|
gnuplotSettings.setY1(plotSettings.getY1());
|
||||||
gnuplotSettings.setY2(plotSettings.getY2());
|
gnuplotSettings.setY2(plotSettings.getY2());
|
||||||
gnuplotSettings.setAggregates(plotSettings.getAggregates());
|
gnuplotSettings.setAggregates(plotSettings.getAggregates());
|
||||||
gnuplotSettings.setKeyOutside(renderOptions.isKeyOutside());
|
gnuplotSettings.setShowKey(renderOptions.isShowKey());
|
||||||
gnuplotSettings.renderLabels(renderOptions.isRenderLabels());
|
gnuplotSettings.renderLabels(renderOptions.isRenderLabels());
|
||||||
gnuplot.plot(gnuplotSettings, dataSeries);
|
gnuplot.plot(gnuplotSettings, dataSeries);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class YAxisTicks {
|
|||||||
} else {
|
} else {
|
||||||
switch (yAxisDefinition.getAxisScale()) {
|
switch (yAxisDefinition.getAxisScale()) {
|
||||||
case LINEAR:
|
case LINEAR:
|
||||||
|
result = computeLinearYTicksNoUnit(height, yRangeMin, yRangeMax);
|
||||||
break;
|
break;
|
||||||
case LOG10:
|
case LOG10:
|
||||||
result = computeLog10YTicksNoUnit(height, yRangeMin, yRangeMax);
|
result = computeLog10YTicksNoUnit(height, yRangeMin, yRangeMax);
|
||||||
@@ -206,25 +207,103 @@ class YAxisTicks {
|
|||||||
|
|
||||||
final List<String> ticsLabels = Arrays.asList(//
|
final List<String> ticsLabels = Arrays.asList(//
|
||||||
"\"1\" 1", //
|
"\"1\" 1", //
|
||||||
|
"\"2\" 2", //
|
||||||
|
"\"5\" 5", //
|
||||||
"\"10\" 10", //
|
"\"10\" 10", //
|
||||||
|
"\"20\" 20", //
|
||||||
|
"\"50\" 50", //
|
||||||
"\"100\" 100", //
|
"\"100\" 100", //
|
||||||
|
"\"200\" 200", //
|
||||||
|
"\"500\" 500", //
|
||||||
"\"1000\" 1000", //
|
"\"1000\" 1000", //
|
||||||
|
"\"2000\" 2000", //
|
||||||
|
"\"5000\" 5000", //
|
||||||
"\"10k\" 10000", //
|
"\"10k\" 10000", //
|
||||||
|
"\"20k\" 20000", //
|
||||||
|
"\"50k\" 50000", //
|
||||||
"\"100k\" 100000", //
|
"\"100k\" 100000", //
|
||||||
|
"\"200k\" 200000", //
|
||||||
|
"\"500k\" 500000", //
|
||||||
"\"1m\" 1000000", //
|
"\"1m\" 1000000", //
|
||||||
|
"\"2m\" 2000000", //
|
||||||
|
"\"5m\" 5000000", //
|
||||||
"\"10m\" 10000000", //
|
"\"10m\" 10000000", //
|
||||||
|
"\"20m\" 20000000", //
|
||||||
|
"\"50m\" 50000000", //
|
||||||
"\"100m\" 100000000", //
|
"\"100m\" 100000000", //
|
||||||
|
"\"200m\" 200000000", //
|
||||||
|
"\"500m\" 500000000", //
|
||||||
"\"1b\" 1000000000.0", //
|
"\"1b\" 1000000000.0", //
|
||||||
|
"\"2b\" 2000000000.0", //
|
||||||
|
"\"5b\" 5000000000.0", //
|
||||||
"\"10b\" 10000000000.0", //
|
"\"10b\" 10000000000.0", //
|
||||||
|
"\"20b\" 20000000000.0", //
|
||||||
|
"\"50b\" 50000000000.0", //
|
||||||
"\"100b\" 100000000000.0", //
|
"\"100b\" 100000000000.0", //
|
||||||
|
"\"200b\" 200000000000.0", //
|
||||||
|
"\"500b\" 500000000000.0", //
|
||||||
"\"1t\" 1000000000000.0", //
|
"\"1t\" 1000000000000.0", //
|
||||||
|
"\"2t\" 2000000000000.0", //
|
||||||
|
"\"5t\" 5000000000000.0", //
|
||||||
"\"10t\" 10000000000000.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;
|
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) {
|
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,
|
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),
|
20_000L, MINUTES.toMillis(1), MINUTES.toMillis(2), MINUTES.toMillis(5), MINUTES.toMillis(10),
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package org.lucares.pdbui;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
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;
|
||||||
import org.lucares.pdb.api.QueryWithCaretMarker.ResultMode;
|
import org.lucares.pdb.api.QueryWithCaretMarker.ResultMode;
|
||||||
import org.lucares.pdb.datastore.Proposal;
|
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.pdb.plot.api.PlotSettings;
|
||||||
import org.lucares.pdbui.domain.AutocompleteProposal;
|
import org.lucares.pdbui.domain.AutocompleteProposal;
|
||||||
import org.lucares.pdbui.domain.AutocompleteProposalByValue;
|
import org.lucares.pdbui.domain.AutocompleteProposalByValue;
|
||||||
@@ -345,6 +350,22 @@ public class PdbController implements HardcodedValues, PropertyKeys {
|
|||||||
return result;
|
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)
|
@PostMapping(path = "/data", consumes = MediaType.MULTIPART_MIXED_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@ResponseStatus(code = HttpStatus.CREATED)
|
@ResponseStatus(code = HttpStatus.CREATED)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class WebConfiguration implements WebMvcConfigurer, HardcodedValues, Prop
|
|||||||
|
|
||||||
addResourceHandlerForPlottedImages(registry);
|
addResourceHandlerForPlottedImages(registry);
|
||||||
|
|
||||||
// addResourceHandlerForAngular(registry);
|
addResourceHandlerForAngular(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addResourceHandlerForPlottedImages(final ResourceHandlerRegistry 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.
|
// to determine which sub-page to show.
|
||||||
//
|
//
|
||||||
// This makes Angular also responsible for all 404 pages.
|
// 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() {
|
.addResolver(new PathResourceResolver() {
|
||||||
@Override
|
@Override
|
||||||
protected Resource getResource(final String resourcePath, final Resource location)
|
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);
|
final Resource requestedResource = location.createRelative(resourcePath);
|
||||||
|
|
||||||
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
|
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
|
||||||
: new ClassPathResource("/resources/index.html");
|
: new ClassPathResource("/resources/browser/index.html");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.lucares.pdb.plot.api.Aggregate;
|
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.Limit;
|
||||||
import org.lucares.pdb.plot.api.YAxisDefinition;
|
import org.lucares.pdb.plot.api.YAxisDefinition;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ public class PlotConfig {
|
|||||||
private YAxisDefinition y1 = new YAxisDefinition();
|
private YAxisDefinition y1 = new YAxisDefinition();
|
||||||
private YAxisDefinition y2 = new YAxisDefinition();
|
private YAxisDefinition y2 = new YAxisDefinition();
|
||||||
|
|
||||||
private String dateRange;
|
private DateValue dateRange;
|
||||||
|
|
||||||
private List<Aggregate> aggregates = new ArrayList<>();
|
private List<Aggregate> aggregates = new ArrayList<>();
|
||||||
|
|
||||||
@@ -66,11 +67,11 @@ public class PlotConfig {
|
|||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDateRange() {
|
public DateValue getDateRange() {
|
||||||
return dateRange;
|
return dateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDateRange(final String dateRange) {
|
public void setDateRange(final DateValue dateRange) {
|
||||||
this.dateRange = dateRange;
|
this.dateRange = dateRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#db.base=D:/ws/pdb/dataNew
|
#db.base=D:/ws/pdb/dataNew
|
||||||
#db.base=D:/ws/pdb2/databases/prod
|
#db.base=D:/ws/pdb2/databases/prod
|
||||||
db.base=D:/ws/pdb/databases/prod
|
db.base=c:/ws/pdb/databases/prod
|
||||||
server.port=17333
|
server.port=80
|
||||||
gnuplot.home=D:/ws/pdb/gnuplot-5.2
|
gnuplot.home=D:/ws/pdb/gnuplot-5.2
|
||||||
cache.images.duration.seconds=86400
|
cache.images.duration.seconds=86400
|
||||||
defaults.groupBy=pod,method,metric
|
defaults.groupBy=pod,method,metric
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
db.base=D:/ws/pdb/databases/test
|
db.base=c:/ws/pdb/databases/test
|
||||||
server.port=17333
|
server.port=80
|
||||||
gnuplot.home=D:/ws/pdb/gnuplot-5.2
|
gnuplot.home=D:/ws/pdb/gnuplot-5.2
|
||||||
cache.images.duration.seconds=86400
|
cache.images.duration.seconds=86400
|
||||||
defaults.groupBy=pod,method,metric
|
defaults.groupBy=pod,method,metric
|
||||||
defaults.splitBy=method
|
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
|
||||||
|
|||||||
@@ -28,15 +28,10 @@ public class VariableByteEncoder {
|
|||||||
public static final long MIN_VALUE = Long.MIN_VALUE / 2 + 1;
|
public static final long MIN_VALUE = Long.MIN_VALUE / 2 + 1;
|
||||||
public static final long MAX_VALUE = Long.MAX_VALUE / 2;
|
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 int CONTINUATION_BYTE_FLAG = 1 << 7; // 10000000
|
||||||
|
|
||||||
private static final long DATA_BITS = (1 << 7) - 1; // 01111111
|
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.
|
* Encodes time and value into the given buffer.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -137,8 +132,18 @@ public class VariableByteEncoder {
|
|||||||
* input: 0 1 -1 2 -2 3 -3
|
* input: 0 1 -1 2 -2 3 -3
|
||||||
* encoded: 1 2 3 4 5 6 7
|
* encoded: 1 2 3 4 5 6 7
|
||||||
* </pre>
|
* </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;
|
return value > 0 ? value * 2 : (value * -2) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,9 +239,9 @@ public class VariableByteEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int neededBytes(final long value) {
|
public static int neededBytes(final long value) {
|
||||||
final byte[] buffer = SINGLE_VALUE_BUFFER.get();
|
final long val = encodeIntoPositiveValue(value);
|
||||||
final int usedBytes = encodeInto(value, buffer, 0);
|
final int numberOfOnes = 64 - Long.numberOfLeadingZeros(val);
|
||||||
return usedBytes;
|
return numberOfOnes / 7 + (numberOfOnes % 7 == 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class VariableByteEncoderTest {
|
|||||||
Assertions.assertEquals(originalValues, decodedValues);
|
Assertions.assertEquals(originalValues, decodedValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stream<Arguments> providerNededBytes() {
|
public static Stream<Arguments> providerNeededBytes() {
|
||||||
return Stream.of( //
|
return Stream.of( //
|
||||||
Arguments.of(0, 1), //
|
Arguments.of(0, 1), //
|
||||||
Arguments.of(-10, 1), //
|
Arguments.of(-10, 1), //
|
||||||
@@ -98,7 +98,7 @@ public class VariableByteEncoderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("providerNededBytes")
|
@MethodSource("providerNeededBytes")
|
||||||
public void testNeededBytes(final long value, final int expectedNeededBytes) {
|
public void testNeededBytes(final long value, final int expectedNeededBytes) {
|
||||||
|
|
||||||
final int neededBytes = VariableByteEncoder.neededBytes(value);
|
final int neededBytes = VariableByteEncoder.neededBytes(value);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// include all projects with a build.gradle
|
include("block-storage")
|
||||||
// (this does not support nested projects)
|
include("data-store")
|
||||||
File srcDir = new File(".")
|
include("pdb-api")
|
||||||
FileCollection collection = files { srcDir.listFiles() }
|
include("pdb-js")
|
||||||
collection.filter{ new File(it, "build.gradle").isFile() }.each{ include it.getName() }
|
include("pdb-plotting")
|
||||||
|
include("pdb-ui")
|
||||||
|
include("pdb-utils")
|
||||||
|
include("performanceDb")
|
||||||
|
|||||||
Reference in New Issue
Block a user