diff options
Diffstat (limited to 'frontend')
19 files changed, 531 insertions, 203 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ebe30390..5029a126 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ "d3-graphviz": "^2.6.1", "file-saver": "^2.0.5", "jquery": "^3.6.0", + "lodash.isequal": "^4.5.0", "material-icons": "^1.10.8", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", @@ -55,6 +56,7 @@ "@types/d3-graphviz": "^2.6.7", "@types/file-saver": "^2.0.5", "@types/jasmine": "~3.10.0", + "@types/lodash.isequal": "^4.5.6", "@types/node": "^12.11.1", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -435,6 +437,7 @@ }, "node_modules/@angular/compiler-cli": { "version": "13.2.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.17.2", @@ -463,6 +466,7 @@ }, "node_modules/@angular/compiler-cli/node_modules/@ampproject/remapping": { "version": "2.1.2", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/trace-mapping": "^0.3.0" @@ -473,6 +477,7 @@ }, "node_modules/@angular/compiler-cli/node_modules/@babel/core": { "version": "7.17.5", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.1.0", @@ -501,6 +506,7 @@ }, "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -508,6 +514,7 @@ }, "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { "version": "7.17.3", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.17.0", @@ -520,6 +527,7 @@ }, "node_modules/@angular/compiler-cli/node_modules/source-map": { "version": "0.5.7", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -742,6 +750,7 @@ }, "node_modules/@babel/core": { "version": "7.16.12", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.16.7", @@ -770,6 +779,7 @@ }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -777,6 +787,7 @@ }, "node_modules/@babel/core/node_modules/source-map": { "version": "0.5.7", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -784,6 +795,7 @@ }, "node_modules/@babel/generator": { "version": "7.16.8", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.16.8", @@ -796,6 +808,7 @@ }, "node_modules/@babel/generator/node_modules/source-map": { "version": "0.5.7", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2821,6 +2834,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "node_modules/@types/lodash.isequal": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz", + "integrity": "sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "dev": true, @@ -3243,6 +3271,7 @@ }, "node_modules/anymatch": { "version": "3.1.2", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -3493,6 +3522,7 @@ }, "node_modules/binary-extensions": { "version": "2.2.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3580,6 +3610,7 @@ }, "node_modules/braces": { "version": "3.0.2", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.0.1" @@ -3757,6 +3788,7 @@ }, "node_modules/chokidar": { "version": "3.5.3", + "dev": true, "funding": [ { "type": "individual", @@ -4699,6 +4731,7 @@ }, "node_modules/dependency-graph": { "version": "0.11.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -4845,6 +4878,7 @@ }, "node_modules/encoding": { "version": "0.1.13", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4853,6 +4887,7 @@ }, "node_modules/encoding/node_modules/iconv-lite": { "version": "0.6.3", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5328,6 +5363,7 @@ }, "node_modules/fill-range": { "version": "7.0.1", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -5576,6 +5612,7 @@ }, "node_modules/glob-parent": { "version": "5.1.2", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -6113,6 +6150,7 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -6162,6 +6200,7 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6176,6 +6215,7 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -6199,6 +6239,7 @@ }, "node_modules/is-number": { "version": "7.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -6891,6 +6932,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "node_modules/log-symbols": { "version": "4.1.0", "dev": true, @@ -6987,6 +7033,7 @@ }, "node_modules/lru-cache": { "version": "6.0.0", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -6997,6 +7044,7 @@ }, "node_modules/magic-string": { "version": "0.25.7", + "dev": true, "license": "MIT", "dependencies": { "sourcemap-codec": "^1.4.4" @@ -7539,6 +7587,7 @@ }, "node_modules/normalize-path": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8175,6 +8224,7 @@ }, "node_modules/picomatch": { "version": "2.3.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -8925,6 +8975,7 @@ }, "node_modules/readdirp": { "version": "3.6.0", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -9199,7 +9250,7 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/sass": { @@ -9319,6 +9370,7 @@ }, "node_modules/semver": { "version": "7.3.5", + "dev": true, "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" @@ -9668,6 +9720,7 @@ }, "node_modules/sourcemap-codec": { "version": "1.4.8", + "dev": true, "license": "MIT" }, "node_modules/spdy": { @@ -10046,6 +10099,7 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -10127,6 +10181,7 @@ }, "node_modules/typescript": { "version": "4.5.5", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10748,6 +10803,7 @@ }, "node_modules/yallist": { "version": "4.0.0", + "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -11033,6 +11089,7 @@ }, "@angular/compiler-cli": { "version": "13.2.5", + "dev": true, "requires": { "@babel/core": "^7.17.2", "chokidar": "^3.0.0", @@ -11048,12 +11105,14 @@ "dependencies": { "@ampproject/remapping": { "version": "2.1.2", + "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.0" } }, "@babel/core": { "version": "7.17.5", + "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -11073,12 +11132,14 @@ }, "dependencies": { "semver": { - "version": "6.3.0" + "version": "6.3.0", + "dev": true } } }, "@babel/generator": { "version": "7.17.3", + "dev": true, "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -11086,7 +11147,8 @@ } }, "source-map": { - "version": "0.5.7" + "version": "0.5.7", + "dev": true } } }, @@ -11197,6 +11259,7 @@ }, "@babel/core": { "version": "7.16.12", + "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -11216,15 +11279,18 @@ }, "dependencies": { "semver": { - "version": "6.3.0" + "version": "6.3.0", + "dev": true }, "source-map": { - "version": "0.5.7" + "version": "0.5.7", + "dev": true } } }, "@babel/generator": { "version": "7.16.8", + "dev": true, "requires": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", @@ -11232,7 +11298,8 @@ }, "dependencies": { "source-map": { - "version": "0.5.7" + "version": "0.5.7", + "dev": true } } }, @@ -12165,8 +12232,7 @@ }, "dependencies": { "ws": { - "version": "7.5.7", - "requires": {} + "version": "7.5.7" } } }, @@ -12178,8 +12244,7 @@ }, "@ngtools/webpack": { "version": "13.2.5", - "dev": true, - "requires": {} + "dev": true }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -12522,6 +12587,21 @@ "version": "7.0.9", "dev": true }, + "@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "@types/lodash.isequal": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz", + "integrity": "sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mime": { "version": "1.3.2", "dev": true @@ -12731,8 +12811,7 @@ }, "acorn-import-assertions": { "version": "1.8.0", - "dev": true, - "requires": {} + "dev": true }, "adjust-sourcemap-loader": { "version": "4.0.0", @@ -12830,6 +12909,7 @@ }, "anymatch": { "version": "3.1.2", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -12981,7 +13061,8 @@ "dev": true }, "binary-extensions": { - "version": "2.2.0" + "version": "2.2.0", + "dev": true }, "bl": { "version": "4.1.0", @@ -13038,8 +13119,7 @@ "dev": true }, "bootstrap": { - "version": "5.1.3", - "requires": {} + "version": "5.1.3" }, "brace-expansion": { "version": "1.1.11", @@ -13050,6 +13130,7 @@ }, "braces": { "version": "3.0.2", + "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -13158,6 +13239,7 @@ }, "chokidar": { "version": "3.5.3", + "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -13179,8 +13261,7 @@ }, "circular-dependency-plugin": { "version": "5.2.2", - "dev": true, - "requires": {} + "dev": true }, "clean-stack": { "version": "2.2.0", @@ -13551,8 +13632,7 @@ }, "css-prefers-color-scheme": { "version": "6.0.3", - "dev": true, - "requires": {} + "dev": true }, "css-select": { "version": "4.2.1", @@ -13752,7 +13832,8 @@ "dev": true }, "dependency-graph": { - "version": "0.11.0" + "version": "0.11.0", + "dev": true }, "destroy": { "version": "1.0.4", @@ -13851,6 +13932,7 @@ }, "encoding": { "version": "0.1.13", + "dev": true, "optional": true, "requires": { "iconv-lite": "^0.6.2" @@ -13858,6 +13940,7 @@ "dependencies": { "iconv-lite": { "version": "0.6.3", + "dev": true, "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -14170,6 +14253,7 @@ }, "fill-range": { "version": "7.0.1", + "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -14318,6 +14402,7 @@ }, "glob-parent": { "version": "5.1.2", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -14514,8 +14599,7 @@ }, "icss-utils": { "version": "5.1.0", - "dev": true, - "requires": {} + "dev": true }, "ieee754": { "version": "1.2.1", @@ -14662,6 +14746,7 @@ }, "is-binary-path": { "version": "2.1.0", + "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -14685,13 +14770,15 @@ "dev": true }, "is-extglob": { - "version": "2.1.1" + "version": "2.1.1", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0" }, "is-glob": { "version": "4.0.3", + "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -14705,7 +14792,8 @@ "dev": true }, "is-number": { - "version": "7.0.0" + "version": "7.0.0", + "dev": true }, "is-path-cwd": { "version": "2.2.0", @@ -15029,8 +15117,7 @@ }, "karma-jasmine-html-reporter": { "version": "1.7.0", - "dev": true, - "requires": {} + "dev": true }, "karma-source-map-support": { "version": "1.4.0", @@ -15138,6 +15225,11 @@ "version": "4.0.8", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "log-symbols": { "version": "4.1.0", "dev": true, @@ -15198,12 +15290,14 @@ }, "lru-cache": { "version": "6.0.0", + "dev": true, "requires": { "yallist": "^4.0.0" } }, "magic-string": { "version": "0.25.7", + "dev": true, "requires": { "sourcemap-codec": "^1.4.4" } @@ -15538,7 +15632,8 @@ } }, "normalize-path": { - "version": "3.0.0" + "version": "3.0.0", + "dev": true }, "normalize-range": { "version": "0.1.2", @@ -15953,7 +16048,8 @@ "version": "1.0.0" }, "picomatch": { - "version": "2.3.1" + "version": "2.3.1", + "dev": true }, "pify": { "version": "2.3.0", @@ -16043,8 +16139,7 @@ }, "postcss-custom-media": { "version": "8.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-custom-properties": { "version": "12.1.4", @@ -16098,13 +16193,11 @@ }, "postcss-font-variant": { "version": "5.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-gap-properties": { "version": "3.0.3", - "dev": true, - "requires": {} + "dev": true }, "postcss-image-set-function": { "version": "4.0.6", @@ -16124,8 +16217,7 @@ }, "postcss-initial": { "version": "4.0.1", - "dev": true, - "requires": {} + "dev": true }, "postcss-lab-function": { "version": "4.1.1", @@ -16146,18 +16238,15 @@ }, "postcss-logical": { "version": "5.0.4", - "dev": true, - "requires": {} + "dev": true }, "postcss-media-minmax": { "version": "5.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-extract-imports": { "version": "3.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -16191,13 +16280,11 @@ }, "postcss-overflow-shorthand": { "version": "3.0.3", - "dev": true, - "requires": {} + "dev": true }, "postcss-page-break": { "version": "3.0.4", - "dev": true, - "requires": {} + "dev": true }, "postcss-place": { "version": "7.0.4", @@ -16254,8 +16341,7 @@ }, "postcss-replace-overflow-wrap": { "version": "4.0.0", - "dev": true, - "requires": {} + "dev": true }, "postcss-selector-not": { "version": "5.0.0", @@ -16383,6 +16469,7 @@ }, "readdirp": { "version": "3.6.0", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -16554,7 +16641,7 @@ }, "safer-buffer": { "version": "2.1.2", - "devOptional": true + "dev": true }, "sass": { "version": "1.49.0", @@ -16598,8 +16685,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "dev": true, - "requires": {} + "dev": true }, "json-schema-traverse": { "version": "0.4.1", @@ -16620,6 +16706,7 @@ }, "semver": { "version": "7.3.5", + "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -16865,7 +16952,8 @@ } }, "sourcemap-codec": { - "version": "1.4.8" + "version": "1.4.8", + "dev": true }, "spdy": { "version": "4.0.2", @@ -17035,8 +17123,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "dev": true, - "requires": {} + "dev": true }, "json-schema-traverse": { "version": "0.4.1", @@ -17090,6 +17177,7 @@ }, "to-regex-range": { "version": "5.0.1", + "dev": true, "requires": { "is-number": "^7.0.0" } @@ -17138,7 +17226,8 @@ "dev": true }, "typescript": { - "version": "4.5.5" + "version": "4.5.5", + "dev": true }, "ua-parser-js": { "version": "0.7.31", @@ -17297,8 +17386,7 @@ }, "ajv-keywords": { "version": "3.5.2", - "dev": true, - "requires": {} + "dev": true }, "json-schema-traverse": { "version": "0.4.1", @@ -17492,8 +17580,7 @@ }, "ws": { "version": "8.2.3", - "dev": true, - "requires": {} + "dev": true }, "xlsx": { "version": "0.18.5", @@ -17511,7 +17598,8 @@ "version": "5.0.8" }, "yallist": { - "version": "4.0.0" + "version": "4.0.0", + "dev": true }, "yaml": { "version": "1.10.2", diff --git a/frontend/package.json b/frontend/package.json index 1596072d..5d32208b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "d3-graphviz": "^2.6.1", "file-saver": "^2.0.5", "jquery": "^3.6.0", + "lodash.isequal": "^4.5.0", "material-icons": "^1.10.8", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", @@ -58,6 +59,7 @@ "@types/d3-graphviz": "^2.6.7", "@types/file-saver": "^2.0.5", "@types/jasmine": "~3.10.0", + "@types/lodash.isequal": "^4.5.6", "@types/node": "^12.11.1", "jasmine-core": "~4.0.0", "karma": "~6.3.0", diff --git a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts index bf5e3fd6..07976da3 100644 --- a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts @@ -28,10 +28,28 @@ export class BoxPlotComponent implements AfterViewInit { updateChart(min: number, max: number, q1: number, q3: number, median: number) { if (this.myChart) { this.boxplotData.datasets[0].data = [[min, q1, median, q3, max]] - this.myChart.update(); + this.myChart?.update(); } + /*this.boxplotData.datasets = [{ + data: [[min, q1, median, q3, max]], + }]*/ + }; + /* + updatePieChart(uniqueValues: string[], uniqueValuesPercent: number[]){ + //console.log(this.uniqueValues, this.uniqueValuesPercent); + this.pieChartData.datasets = [{ + label: "%", + backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850", "#000000"], + data: uniqueValuesPercent, + }]; + this.pieChartData.labels = uniqueValues + console.log(this.uniqueValues, this.uniqueValuesPercent); + this.myChart?.update() + }; + */ + @ViewChild('boxplot') chartRef!: ElementRef; constructor() { //this.updateChart(); diff --git a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts index cba5252a..9264e41c 100644 --- a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts @@ -58,10 +58,6 @@ export class PieChartComponent implements AfterViewInit { data: data , options: { - /*title: { - display: true, - text: 'Predicted world population (millions) in 2050' - }*/ plugins:{ legend: { display: false diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css index 9e7644b6..97c13299 100644 --- a/frontend/src/app/_elements/folder/folder.component.css +++ b/frontend/src/app/_elements/folder/folder.component.css @@ -105,8 +105,9 @@ .bottom-button { font-size: large; position: relative; + text-align: center; background-color: var(--ns-primary); - width: 10rem; + width: 12rem; height: 2.3rem; border-color: var(--ns-primary); border-style: solid; @@ -170,7 +171,8 @@ .folder-inside { width: 100%; - min-height: 40rem; + min-height: 33rem; + height: 100%; max-height: 100%; } diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html index 97ae0d23..da182945 100644 --- a/frontend/src/app/_elements/folder/folder.component.html +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -29,47 +29,48 @@ <button matSuffix class="btn-clear input-icon" (click)="clearSearchTerm()"><mat-icon>clear</mat-icon></button> </mat-form-field> </div> - <div id="search-options"> - <!-- <div id="collapseFilters" class="collapse collapse-horizontal"> + <div id="modelFilter" *ngIf="type == FolderType.Model && forExperiment"> + Filter: {{forExperiment.type}} + </div> + <!--<div id="search-options"> + <div id="collapseFilters" class="collapse collapse-horizontal"> <mat-icon class="text-offwhite ">timeline</mat-icon> Regresioni <mat-icon class="text-offwhite ">looks_two</mat-icon> Binarni klasifikacioni <mat-icon class="text-offwhite ">auto_awesome_motion</mat-icon> Multiklasifikacioni - </div> --> + </div> <button class="btn-clear icon-toggle" data-bs-toggle="collapse" data-bs-target="#collapseFilters" aria-expanded="false" aria-controls="collapseFilters"> <mat-icon>filter_alt</mat-icon> </button> - <!-- <div id="collapseSort" class="collapse collapse-horizontal"> + <div id="collapseSort" class="collapse collapse-horizontal"> [sort options here TODO] - </div> --> + </div> <button class="btn-clear icon-toggle" data-bs-toggle="collapse" data-bs-target="#collapseSort" aria-expanded="false" aria-controls="collapseSort"> <mat-icon>sort</mat-icon> </button> - <!-- <button class="btn-clear icon-toggle separator" [ngClass]="{'icon-toggle-on': listView}" (click)="toggleListView()"> + <button class="btn-clear icon-toggle separator" [ngClass]="{'icon-toggle-on': listView}" (click)="toggleListView()"> <mat-icon>view_list</mat-icon> - </button> --> - </div> + </button> + </div>--> </div> <!--{{fileToDisplay ? fileToDisplay.name : 'No file selected.'}} {{selectedFileIndex}} {{hoveringOverFileIndex}}--> <div class="folder-inside bg-blur"> <div class="file-content" [ngClass]="{'form-hidden' : listView}"> <div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile"> - <button *ngIf="this.selectedFile && selectedTab == TabType.File" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)"> + <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)"> <mat-icon matTooltip="Obriši" matTooltipPosition="right">delete</mat-icon> </button> <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(this.selectedFile,$event)" style="display: inline-block;"> <mat-icon matTooltip="Preuzmi" matTooltipPosition="before">download</mat-icon> </button> - - <!-- <button class="btn-clear file-button"> <mat-icon>zoom_out_map</mat-icon> </button> --> </div> - <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)"></app-form-model> - <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment"></app-form-dataset> + <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)" (editEvent)="onFileChange()"></app-form-model> + <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset> </div> <div [ngClass]="{'form-hidden' : !listView}" class="list-view"> <div *ngFor="let file of filteredFiles; let i = index"> @@ -107,7 +108,7 @@ {{predictor.lastUpdated | date}} </div> <div class="mx-2 hover-show"> - <button class="btn-clear file-button" (click)="deleteFile(predictor, $event)"> + <button class="btn-clear file-button" (click)="deleteFile(predictor, $event, true)"> <mat-icon>delete</mat-icon> </button> </div> @@ -132,14 +133,30 @@ </div> </div> </button> - <button mat-button (click)="ok()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="false"> - <div class="f-row"> - <div>Ok</div> - <div class="icon-double pt-1"> - <mat-icon>check</mat-icon> - <mat-icon>check</mat-icon> + <ng-container *ngSwitchCase="false"> + <button mat-button (click)="ok()" class="bottom-button text-offwhite rounded-bottom" *ngIf="!selectedFileHasChanges"> + <div class="f-row"> + <div>Ok</div> + <div class="p-1 w-100" *ngIf="loadingAction"> + <app-spinner></app-spinner> + </div> + <div class="icon-double pt-1" *ngIf="!loadingAction"> + <mat-icon>check</mat-icon> + <mat-icon>check</mat-icon> + </div> </div> - </div> - </button> + </button> + <button mat-button (click)="updateFile()" class="bottom-button text-offwhite rounded-bottom" *ngIf="selectedFileHasChanges" [disabled]="loadingAction"> + <div class="f-row"> + <div *ngIf="!loadingAction">Sačuvaj izmene</div> + <div class="pt-1" *ngIf="!loadingAction"> + <mat-icon>edit</mat-icon> + </div> + <div class="pt-1 w-100" *ngIf="loadingAction"> + <app-spinner></app-spinner> + </div> + </div> + </button> + </ng-container> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts index d2175f73..beff6012 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -14,6 +14,7 @@ import { FormModelComponent } from '../form-model/form-model.component'; import { ActivatedRoute, Router } from '@angular/router'; import Predictor from 'src/app/_data/Predictor'; import FileSaver from 'file-saver'; +import isEqual from 'lodash.isequal'; @Component({ selector: 'app-folder', @@ -105,6 +106,7 @@ export class FolderComponent implements AfterViewInit { selectFile(file?: FolderFile) { this.formDataset.resetPagging(); this.selectedFile = file; + Object.assign(this.lastFileData, this.selectedFile); this.fileToDisplay = file; if (this.type == FolderType.Experiment && file) { this.router.navigate(['/experiment/' + file._id]); @@ -118,7 +120,7 @@ export class FolderComponent implements AfterViewInit { if (this.type == FolderType.Dataset) this.formDataset.loadExisting(); } - + goToExperimentPageWithPredictor(file: FolderFile, predictor: Predictor) { this.router.navigate(['/experiment/p/' + predictor._id]); } @@ -138,7 +140,7 @@ export class FolderComponent implements AfterViewInit { _initialized: boolean = false; refreshFiles(selectedDatasetId: string | null = null, selectedModelId: string | null = null) { - + this.tabsToShow.forEach(tab => { this.folders[tab] = []; }); @@ -221,7 +223,7 @@ export class FolderComponent implements AfterViewInit { this.predictorsForExp[exp._id].forEach(pred => { const model = this.folders[TabType.MyModels].find(model => model._id == pred.modelId); pred.name = model?.name!; - pred.lastUpdated = model?.lastUpdated!; + //pred.lastUpdated = model?.lastUpdated!; }) /* ------------------------------------------------ */ this.searchTermsChanged(); @@ -267,7 +269,12 @@ export class FolderComponent implements AfterViewInit { searchTermsChanged() { this.filteredFiles.length = 0; if (!this.files) return; - this.filteredFiles.push(...this.files.filter((file) => file.name.toLowerCase().includes(this.searchTerm.toLowerCase()))); + this.filteredFiles.push(...this.files.filter((file) => { + return (file.name.toLowerCase().includes(this.searchTerm.toLowerCase()) + && (!this.forExperiment + || this.type != FolderType.Model + || (this.type == FolderType.Model && (<Model>file).type == this.forExperiment.type))) + })); /*if (this.selectedFile) { if (!this.filteredFiles.includes(this.selectedFile)) { if (this.hoverTab === TabType.None && this.getFolderType(this.selectedTab) === this.type) { @@ -282,41 +289,102 @@ export class FolderComponent implements AfterViewInit { listView: boolean = true; - deleteFile(file: FolderFile, event: Event) { - event.stopPropagation(); - this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); - this.files.splice(this.files.indexOf(file), 1); + loadingAction = false; + selectedFileHasChanges = false; + lastFileData = {}; + + onFileChange() { + console.log(this.selectedFile, this.lastFileData) + setTimeout(() => { + this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || isEqual(this.selectedFile, this.lastFileData)); + }); + } + + updateFile() { + const file = this.selectedFile; + this.loadingAction = true; switch (this.type) { case FolderType.Dataset: - this.datasetsService.deleteDataset(<Dataset>file).subscribe((response) => { - Shared.openDialog("Obaveštenje", "Uspešno ste obrisali odabrani izvor podataka."); - //this.filteredFiles.splice(this.files.indexOf(file), 1); - //this.refreshFiles(); + this.datasetsService.editDataset(<Dataset>file).subscribe((response) => { + this.fileUpdatedSuccess(); }); break; case FolderType.Model: - this.modelsService.deleteModel(<Model>file).subscribe((response) => { - Shared.openDialog("Obaveštenje", "Uspešno ste obrisali odabranu konfiguraciju neuronske mreže."); - //this.refreshFiles(); + this.modelsService.editModel(<Model>file).subscribe((response) => { + this.fileUpdatedSuccess(); }); break; + } + } + + fileUpdatedSuccess() { + this.loadingAction = false; + this.selectedFileHasChanges = false; + Object.assign(this.lastFileData, this.selectedFile); + this.refreshFiles(); + } + + deleteFile(file: FolderFile, event: Event, deletePredictor: boolean = false) { + event.stopPropagation(); + + switch (this.type) { + case FolderType.Dataset: + const dataset = <Dataset>file; + Shared.openYesNoDialog("Obriši izvor podataka", "Eksperimenti i trenirani modeli nad ovim izvorom podataka će takođe biti obrisani, da li ste sigurni da želite da obrišete izvor: " + dataset.name + "?", () => { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + this.loadingAction = true; + this.datasetsService.deleteDataset(dataset).subscribe((response) => { + this.loadingAction = false; + }); + }) + break; + case FolderType.Model: + const model = <Model>file; + Shared.openYesNoDialog("Obriši konfiguraciju neuronske mreže", "Trenirani modeli za ovu konfiguraciju će takođe biti obrisani, da li ste sigurni da želite da obrišete konfiguraciju: " + model.name + "?", () => { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + this.loadingAction = true; + this.modelsService.deleteModel(<Model>file).subscribe((response) => { + this.loadingAction = false; + }); + }) + + break; case FolderType.Experiment: - // this.experimentsService.deleteExperiment(<Model>file).subscribe((response) => { - // console.log(response); - // }); - //todo delete za predictor + if (deletePredictor) { + const predictor = <Predictor>file; + Shared.openYesNoDialog("Obriši trenirani model", "Da li ste sigurni da želite da obrišete trenirani model: " + predictor.name + "?", () => { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + this.loadingAction = true; + this.predictorsService.deletePredictor(predictor).subscribe((response) => { + this.loadingAction = false; + }); + }); + } else { + const experiment = <Experiment>file; + Shared.openYesNoDialog("Obriši eksperiment", "Trenirani modeli za ovaj eksperiment će takođe biti obrisani, da li ste sigurni da želite da obrišete eksperiment: " + experiment.name + "?", () => { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.files.splice(this.files.indexOf(file), 1); + this.loadingAction = true; + this.experimentsService.deleteExperiment(experiment).subscribe((response) => { + this.loadingAction = false; + }); + }); + } break; } } downloadFile(file: FolderFile, event: Event) { event.stopPropagation(); - if (this.type==FolderType.Dataset) { - const fileId=(<Dataset>file).fileId; - const name=(<Dataset>file).name; - const ext=(<Dataset>file).extension; - if(fileId!=undefined) - this.datasetsService.downloadFile(fileId).subscribe((response)=>{ - FileSaver.saveAs(response,name+ext); + if (this.type == FolderType.Dataset) { + const fileId = (<Dataset>file).fileId; + const name = (<Dataset>file).name; + const ext = (<Dataset>file).extension; + if (fileId != undefined) + this.datasetsService.downloadFile(fileId).subscribe((response) => { + FileSaver.saveAs(response, name + ext); }); @@ -333,7 +401,7 @@ export class FolderComponent implements AfterViewInit { this.datasetsService.stealDataset(<Dataset>file).subscribe((response) => { Shared.openDialog("Obaveštenje", "Uspešno ste dodali javni izvor podataka u vašu kolekciju."); this.refreshFiles(null); - }, (error:any) => { + }, (error: any) => { if (error.error == "Dataset with this name already exists") { Shared.openDialog("Obaveštenje", "Izvor podataka sa ovim imenom postoji u vašoj kolekciji."); } @@ -343,7 +411,7 @@ export class FolderComponent implements AfterViewInit { this.modelsService.stealModel(<Model>file).subscribe((response) => { Shared.openDialog("Obaveštenje", "Uspešno ste dodali javnu konfiguraciju neuronske mreže u vašu kolekciju."); this.refreshFiles(null); - }, (error:any) => { + }, (error: any) => { if (error.error == "Model already exisits or validation size is not between 0-1") { Shared.openDialog("Obaveštenje", "Model sa ovim imenom postoji u vašoj kolekciji."); } diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html index 7be838f1..78bb4bd1 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html @@ -1,49 +1,48 @@ <div class="folderBox" *ngIf="dataset"> - - <div class="topBar"> - <div class="kolona mb-3"> - <div class="fileButton"> - <button type="button" mat-raised-button (click)="fileInput.click()"><span *ngIf="!firstInput">Dodaj izvor podataka</span><span *ngIf="firstInput">{{filename}}</span></button> - </div> - </div> + <div class="topBar"> + <div class="kolona mb-3"> + <div class="fileButton"> + <button type="button" mat-raised-button (click)="fileInput.click()"><span *ngIf="!firstInput">Dodaj izvor podataka</span><span *ngIf="firstInput">{{filename}}</span></button> + </div> + </div> - <div class="kolona"> - <div role="group"> - <mat-form-field class="example-full-width" appearance="fill"> - <mat-label>Naziv</mat-label> - <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name"> + <div class="kolona"> + <div role="group"> + <mat-form-field class="example-full-width" appearance="fill"> + <mat-label>Naziv</mat-label> + <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (change)="editEvent.emit()"> - <mat-error *ngIf="nameFormControl.hasError('required')"> - Naziv je <strong>obavezan</strong> - </mat-error> - </mat-form-field> - </div> - </div> - <div class="kolona"> - <mat-form-field appearance="fill"> - <mat-label>Delimiter</mat-label> - <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update()" value=","> - <mat-option *ngFor="let option of delimiterOptions" [value]="option"> - {{ option }} - </mat-option> - </mat-select> - </mat-form-field> - </div> + <mat-error *ngIf="nameFormControl.hasError('required')"> + Naziv je <strong>obavezan</strong> + </mat-error> + </mat-form-field> + </div> + </div> + <div class="kolona"> + <mat-form-field appearance="fill"> + <mat-label>Delimiter</mat-label> + <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value=","> + <mat-option *ngFor="let option of delimiterOptions" [value]="option"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> </div> - + </div> + <div class="row" style="margin-right: 0;"> <div class="file-container" [ngClass]="{'dottedClass': !tableData.hasInput}"> <i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i> - - <input class="file" id="file-upload" (change)="changeListener($event)" #fileInput type="file" accept=".csv"> + + <input class="file" id="file-upload" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv"> <div class="mt-5 datatable"> <div [ngClass]="{'hidden': (!existingFlag)}" class="text-center"> - <button mat-button (click)="goBack()"><mat-icon>keyboard_arrow_left</mat-icon></button> + <button mat-button (click)="goBack()"><mat-icon>keyboard_arrow_left</mat-icon></button> <div style="display: inline;">{{(this.begin/10)+1}}...{{getPage()}}</div> <button mat-button (click)="goForward()"><mat-icon>keyboard_arrow_right</mat-icon></button> </div> diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts index 19c0083c..35d68526 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -24,11 +24,12 @@ export class FormDatasetComponent { files: File[] = []; rowsNumber: number = 0; colsNumber: number = 0; - begin:number=0; - step:number=10; - existingFlag:boolean=false; + begin: number = 0; + step: number = 10; + existingFlag: boolean = false; @Input() dataset: Dataset; //dodaj ! potencijalno + @Output() editEvent = new EventEmitter(); tableData: TableData = new TableData(); @@ -43,31 +44,30 @@ export class FormDatasetComponent { } //@ViewChild('fileImportInput', { static: false }) fileImportInput: any; cemu je ovo sluzilo? - resetPagging(){ - this.begin=0; + resetPagging() { + this.begin = 0; } - goBack(){ - if(this.begin-10<0) - this.begin=0; - else - { - this.begin-=10; + goBack() { + if (this.begin - 10 < 0) + this.begin = 0; + else { + this.begin -= 10; this.loadExisting(); } } - goForward(){ - this.begin+=10; - if(this.dataset.rowCount<this.begin) - this.begin-=10; + goForward() { + this.begin += 10; + if (this.dataset.rowCount < this.begin) + this.begin -= 10; else this.loadExisting(); } - clear(){ + clear() { this.tableData.hasInput = false; } - getPage(){ - return Math.ceil(this.dataset.rowCount/10) + getPage() { + return Math.ceil(this.dataset.rowCount / 10) } changeListener($event: any): void { @@ -81,7 +81,7 @@ export class FormDatasetComponent { this.filename = this.files[0].name; this.tableData.loaded = false; - this.existingFlag=false; + this.existingFlag = false; this.update(); } @@ -115,30 +115,30 @@ export class FormDatasetComponent { this.dataset.name = this.filename.slice(0, this.filename.length - 4); } - loadExisting(){ - this.existingFlag=true; + loadExisting() { + this.existingFlag = true; this.firstInput = false; this.tableData.hasInput = true; this.tableData.loaded = false; - this.datasetsService.getDatasetHeader(this.dataset.fileId).subscribe((header: string | undefined)=>{ - - this.datasetsService.getDatasetFilePaging(this.dataset.fileId,this.begin,this.step).subscribe((file: string | undefined) => { - if (file) { - this.tableData.loaded = true; - this.tableData.numRows = this.dataset.rowCount; - this.tableData.numCols = this.dataset.columnInfo.length; - this.tableData.data = this.csv.csvToArray(header+'\n'+file, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter); - - } - else{ - this.begin-=10; - this.loadExisting(); - } + this.datasetsService.getDatasetHeader(this.dataset.fileId).subscribe((header: string | undefined) => { + + this.datasetsService.getDatasetFilePaging(this.dataset.fileId, this.begin, this.step).subscribe((file: string | undefined) => { + if (file) { + this.tableData.loaded = true; + this.tableData.numRows = this.dataset.rowCount; + this.tableData.numCols = this.dataset.columnInfo.length; + this.tableData.data = this.csv.csvToArray(header + '\n' + file, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter); + + } + else { + this.begin -= 10; + this.loadExisting(); + } + }); }); - }); - + } /*exportAsXLSX():void { diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html index c7a9d5ca..bed69998 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -5,13 +5,13 @@ <div class="ns-col"> <mat-form-field class="example-full-width" appearance="fill" class="mat-fix"> <mat-label>Naziv</mat-label> - <input type="text" matInput [(ngModel)]="newModel.name"> + <input type="text" matInput [(ngModel)]="newModel.name" (valueChange)="editEvent.emit()"> </mat-form-field> </div> <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Tip problema</mat-label> - <mat-select [(ngModel)]="forProblemType" [disabled]="this.hideProblemType"> + <mat-select [(ngModel)]="forProblemType" [disabled]="this.hideProblemType" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> {{ optionName }} </mat-option> @@ -24,7 +24,7 @@ <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Optimizacija</mat-label> - <mat-select [(ngModel)]="newModel.optimizer"> + <mat-select [(ngModel)]="newModel.optimizer" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> {{ optionName }} </mat-option> @@ -35,7 +35,7 @@ <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Funkcija troška</mat-label> - <mat-select [(ngModel)]="newModel.lossFunction"> + <mat-select [(ngModel)]="newModel.lossFunction" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(lossFunctions[forProblemType]); let optionName of Object.values(lossFunctions[forProblemType])" [value]="option"> {{ optionName }} </mat-option> @@ -48,7 +48,7 @@ <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Funkcija aktivacije izlaznog sloja</mat-label> - <mat-select name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <mat-select name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> {{ optionName }} </mat-option> @@ -58,7 +58,7 @@ <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Stopa učenja</mat-label> - <mat-select [(ngModel)]="newModel.learningRate"> + <mat-select [(ngModel)]="newModel.learningRate" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(LearningRate); let optionName of Object.values(LearningRate)" [value]="option"> {{ optionName }} </mat-option> @@ -71,14 +71,14 @@ <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Broj epoha</mat-label> - <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000"> + <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000" (valueChange)="editEvent.emit()"> </mat-form-field> </div> <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Broj uzoraka po iteraciji</mat-label> - <mat-select matNativeControl [(value)]="newModel.batchSize"> + <mat-select matNativeControl [(value)]="newModel.batchSize" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(BatchSize); let optionName of Object.values(BatchSize)" [value]="option">{{option}}</mat-option> </mat-select> </mat-form-field> @@ -102,7 +102,7 @@ </div> <div class="slider-extended"> <div class="slider-pad"></div> - <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)"></mat-slider> + <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)" (valueChange)="editEvent.emit()"></mat-slider> <div class="slider-pad"></div> </div> <div> @@ -110,13 +110,13 @@ </div> <div class="slider-extended"> <div class="slider-pad"></div> - <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="validationSize" (input)="updateValidation($event)"></mat-slider> + <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="validationSize" (input)="updateValidation($event)" (valueChange)="editEvent.emit()"></mat-slider> <div class="slider-pad"></div> </div> </div> <div class="d-flex justify-content-center mt-3"> - <mat-checkbox color="accent">Nasumični redosled podataka</mat-checkbox> + <mat-checkbox color="accent" (valueChange)="editEvent.emit()">Nasumični redosled podataka</mat-checkbox> </div> </div> @@ -220,7 +220,7 @@ <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Regularizacija</mat-label> - <mat-select [(ngModel)]="newModel.layers[i].regularisation"> + <mat-select [(ngModel)]="newModel.layers[i].regularisation" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> {{ optionName }} </mat-option> @@ -229,7 +229,7 @@ <mat-form-field appearance="fill" class="mat-fix"> <mat-label>Stopa regularizacije</mat-label> - <mat-select [(ngModel)]="newModel.layers[i].regularisationRate"> + <mat-select [(ngModel)]="newModel.layers[i].regularisationRate" (valueChange)="editEvent.emit()"> <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> {{ optionName }} </mat-option> diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts index 9e6082c4..196d575b 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -24,6 +24,8 @@ export class FormModelComponent implements AfterViewInit { this.forProblemType = ProblemType.BinaryClassification; } + @Output() editEvent = new EventEmitter(); + ngAfterViewInit(): void { } selectFormControl = new FormControl('', Validators.required); @@ -80,6 +82,7 @@ export class FormModelComponent implements AfterViewInit { this.newModel.layers.splice(this.newModel.layers.length - 1, 1); this.newModel.hiddenLayers -= 1; this.updateGraph(); + this.editEvent.emit(); } } @@ -89,6 +92,7 @@ export class FormModelComponent implements AfterViewInit { this.newModel.hiddenLayers += 1; this.updateGraph(); + this.editEvent.emit(); } } @@ -100,6 +104,7 @@ export class FormModelComponent implements AfterViewInit { if (this.newModel.layers[index].neurons > 1) { this.newModel.layers[index].neurons -= 1; this.updateGraph(); + this.editEvent.emit(); } } @@ -107,6 +112,7 @@ export class FormModelComponent implements AfterViewInit { if (this.newModel.layers[index].neurons < 18) { this.newModel.layers[index].neurons += 1; this.updateGraph(); + this.editEvent.emit(); } } @@ -119,22 +125,26 @@ export class FormModelComponent implements AfterViewInit { for (let i = 0; i < this.newModel.layers.length; i++) { this.newModel.layers[i].activationFunction = this.selectedActivation; } + this.editEvent.emit(); } changeAllRegularisation() { for (let i = 0; i < this.newModel.layers.length; i++) { this.newModel.layers[i].regularisation = this.selectedRegularisation; } + this.editEvent.emit(); } changeAllRegularisationRate() { for (let i = 0; i < this.newModel.layers.length; i++) { this.newModel.layers[i].regularisationRate = this.selectedRegularisationRate; } + this.editEvent.emit(); } changeAllNumberOfNeurons() { for (let i = 0; i < this.newModel.layers.length; i++) { this.newModel.layers[i].neurons = this.selectedNumberOfNeurons; - this.updateGraph(); } + this.updateGraph(); + this.editEvent.emit(); } updateTestSet(event: MatSliderChange) { this.testSetDistribution = event.value!; @@ -149,5 +159,6 @@ export class FormModelComponent implements AfterViewInit { updateValidation(event: MatSliderChange) { this.validationSize = event.value!; + this.editEvent.emit(); } } diff --git a/frontend/src/app/_elements/spinner/spinner.component.css b/frontend/src/app/_elements/spinner/spinner.component.css new file mode 100644 index 00000000..78adc872 --- /dev/null +++ b/frontend/src/app/_elements/spinner/spinner.component.css @@ -0,0 +1,78 @@ +.wrap { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.loader { + color: #ffffff; + font-size: 20px; + margin: auto; + width: 1em; + height: 1em; + border-radius: 50%; + text-indent: -9999em; + -webkit-animation: load4 1.3s infinite linear; + animation: load4 1.3s infinite linear; + -webkit-transform: scale(0.2); + -ms-transform: scale(0.2); + transform: scale(0.2); +} + +@-webkit-keyframes load4 { + 0%, + 100% { + box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0; + } + 12.5% { + box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 25% { + box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 37.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 50% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 62.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em; + } + 75% { + box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0; + } + 87.5% { + box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em; + } +} + +@keyframes load4 { + 0%, + 100% { + box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0; + } + 12.5% { + box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 25% { + box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em; + } + 37.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 50% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em; + } + 62.5% { + box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em; + } + 75% { + box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0; + } + 87.5% { + box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em; + } +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/spinner/spinner.component.html b/frontend/src/app/_elements/spinner/spinner.component.html new file mode 100644 index 00000000..c655abf0 --- /dev/null +++ b/frontend/src/app/_elements/spinner/spinner.component.html @@ -0,0 +1,3 @@ +<div class="wrap"> + <div class="loader">Loading...</div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/spinner/spinner.component.spec.ts b/frontend/src/app/_elements/spinner/spinner.component.spec.ts new file mode 100644 index 00000000..061f78d5 --- /dev/null +++ b/frontend/src/app/_elements/spinner/spinner.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SpinnerComponent } from './spinner.component'; + +describe('SpinnerComponent', () => { + let component: SpinnerComponent; + let fixture: ComponentFixture<SpinnerComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SpinnerComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SpinnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/spinner/spinner.component.ts b/frontend/src/app/_elements/spinner/spinner.component.ts new file mode 100644 index 00000000..f0080edd --- /dev/null +++ b/frontend/src/app/_elements/spinner/spinner.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-spinner', + templateUrl: './spinner.component.html', + styleUrls: ['./spinner.component.css'] +}) +export class SpinnerComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/_pages/experiment/experiment.component.html b/frontend/src/app/_pages/experiment/experiment.component.html index e807dd06..17a6539d 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.html +++ b/frontend/src/app/_pages/experiment/experiment.component.html @@ -42,7 +42,7 @@ <mat-step [completed]="this.step4"> <ng-template matStepLabel> <span *ngIf="!this.step3" class="align-middle"><mat-icon>lock</mat-icon></span> - <span class="label text-overflow">Pregled rezultata<br> treniranja</span> + <span class="label text-overflow align-middle">Pregled rezultata<br> treniranja</span> </ng-template> <p class="text-left text-overflow">Pregledajte tok treniranja i<br> grafički prikaz rezultata</p> </mat-step> diff --git a/frontend/src/app/_services/experiments.service.ts b/frontend/src/app/_services/experiments.service.ts index f4473c8c..4b209ff0 100644 --- a/frontend/src/app/_services/experiments.service.ts +++ b/frontend/src/app/_services/experiments.service.ts @@ -27,4 +27,8 @@ export class ExperimentsService { updateExperiment(experiment: Experiment): Observable<Experiment> { return this.http.put<Experiment>(`${Configuration.settings.apiURL}/experiment/` + experiment._id, experiment, { headers: this.authService.authHeader() }); } + + deleteExperiment(experiment: Experiment) { + return this.http.delete(`${Configuration.settings.apiURL}/experiment/` + experiment._id, { headers: this.authService.authHeader(), responseType: "text" }); + } } diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts index f5d95ec1..2b8fe8f2 100644 --- a/frontend/src/app/_services/models.service.ts +++ b/frontend/src/app/_services/models.service.ts @@ -51,7 +51,7 @@ export class ModelsService { } editModel(model: Model): Observable<Model> { - return this.http.put<Model>(`${Configuration.settings.apiURL}/model/`, model, { headers: this.authService.authHeader() }); + return this.http.put<Model>(`${Configuration.settings.apiURL}/model/` + model.name, model, { headers: this.authService.authHeader() }); } deleteModel(model: Model) { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d44bf6ad..89d53115 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -52,6 +52,7 @@ import { DoughnutChartComponent } from './_elements/_charts/doughnut-chart/dough import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component'; import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; +import { SpinnerComponent } from './_elements/spinner/spinner.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); @@ -91,7 +92,8 @@ export function initializeApp(appConfig: Configuration) { HeatmapComponent, MetricViewComponent, LineChartComponent, - SaveExperimentDialogComponent + SaveExperimentDialogComponent, + SpinnerComponent ], imports: [ BrowserModule, |