diff options
35 files changed, 1096 insertions, 384 deletions
diff --git a/frontend/angular.json b/frontend/angular.json index d1983d31..13c27f40 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -1,117 +1,113 @@ { - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "frontend": { - "projectType": "application", - "schematics": { - "@schematics/angular:application": { - "strict": true - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "outputPath": "dist/frontend", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "src/custom-theme.scss", - "node_modules/bootstrap/dist/css/bootstrap.min.css", - "src/styles.css", - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css" - ], - "scripts": [ - "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" - ] - }, - "configurations": { - "production": { - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "4mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "10kb", - "maximumError": "15kb" - } - ], - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "frontend": { + "projectType": "application", + "schematics": { + "@schematics/angular:application": { + "strict": true } - ], - "outputHashing": "all" }, - "development": { - "buildOptimizer": false, - "optimization": false, - "vendorChunk": true, - "extractLicenses": false, - "sourceMap": true, - "namedChunks": true - } - }, - "defaultConfiguration": "production" - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "configurations": { - "production": { - "browserTarget": "frontend:build:production" - }, - "development": { - "browserTarget": "frontend:build:development" + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/frontend", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "src/styles.css", + "src/custom-theme.scss" + ], + "scripts": [ + "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + ] + }, + "configurations": { + "production": { + "budgets": [{ + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "4mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "10kb", + "maximumError": "15kb" + } + ], + "fileReplacements": [{ + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + }], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "frontend:build:production" + }, + "development": { + "browserTarget": "frontend:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "frontend:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.css" + ], + "scripts": [] + } + } } - }, - "defaultConfiguration": "development" - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "frontend:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets" - ], - "styles": [ - "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", - "src/styles.css" - ], - "scripts": [] - } } - } - } - }, - "defaultProject": "frontend", - "cli": { - "warnings": { - "versionMismatch": false + }, + "defaultProject": "frontend", + "cli": { + "warnings": { + "versionMismatch": false + } } - } }
\ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b997f7c2..a62a1117 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3433,9 +3433,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -14406,9 +14406,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" diff --git a/frontend/src/app/Shared.ts b/frontend/src/app/Shared.ts index 59a2716d..d088fff9 100644 --- a/frontend/src/app/Shared.ts +++ b/frontend/src/app/Shared.ts @@ -1,4 +1,4 @@ -import { ElementRef } from "@angular/core"; +import { ElementRef, EventEmitter } from "@angular/core"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; @@ -27,18 +27,24 @@ class Shared { }); } } - openYesNoDialog(title: string, message: string,yesFunction:Function): void { + openYesNoDialog(title: string, message: string, yesFunction: Function): void { if (this.dialog) { const dialogRef = this.dialog.open(YesNoDialogComponent, { width: '350px', - data: { title: title, message: message,yesFunction} + data: { title: title, message: message, yesFunction } }); dialogRef.afterClosed().subscribe(res => { //nesto }); } } + + bgScroll: EventEmitter<number> = new EventEmitter(); + + emitBGScrollEvent(value: number) { + this.bgScroll.emit(value); + } } export default new Shared(false);
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index b273f56a..00ac0d0c 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -14,14 +14,16 @@ export default class Model { public optimizer: Optimizer = Optimizer.Adam, public lossFunction: LossFunction = LossFunction.MeanSquaredError, public inputNeurons: number = 1, - public hiddenLayerNeurons: number = 1, + public hiddenLayerNeurons: number=1, public hiddenLayers: number = 1, public batchSize: number = 5, public hiddenLayerActivationFunctions: string[] = ['sigmoid'], public outputLayerActivationFunction: ActivationFunction = ActivationFunction.Sigmoid, public uploaderId: string = '', public metrics: string[] = [], // TODO add to add-model form - public epochs: number = 5 // TODO add to add-model form + public epochs: number = 5, // TODO add to add-model form + public inputColNum:number=5, + public learningRate:number=0.01 ) { } } @@ -156,4 +158,4 @@ export enum MetricsMultiClassification { Precision = 'precision_score', Recall = 'recall_score', F1 = 'f1_score', -} +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html index 20cf6487..2006eada 100644 --- a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html @@ -1 +1 @@ -<p>box-plot works!</p> +<p>Box-plot</p>
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.css b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.css diff --git a/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.html b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.html new file mode 100644 index 00000000..4befc7dc --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.html @@ -0,0 +1 @@ +<canvas #doughnut width="800" height="450"></canvas> diff --git a/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.spec.ts b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.spec.ts new file mode 100644 index 00000000..67309670 --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DoughnutChartComponent } from './doughnut-chart.component'; + +describe('DoughnutChartComponent', () => { + let component: DoughnutChartComponent; + let fixture: ComponentFixture<DoughnutChartComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DoughnutChartComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DoughnutChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.ts b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.ts new file mode 100644 index 00000000..4c7508fe --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.ts @@ -0,0 +1,34 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'node_modules/chart.js'; + +@Component({ + selector: 'app-doughnut-chart', + templateUrl: './doughnut-chart.component.html', + styleUrls: ['./doughnut-chart.component.css'] +}) +export class DoughnutChartComponent implements AfterViewInit { + + @ViewChild('doughnut') chartRef!: ElementRef; + constructor() { } + + ngAfterViewInit(): void { + const myChart = new Chart(this.chartRef.nativeElement, { + type: 'doughnut', + data: { + labels: ["Africa", "Asia", "Europe", "Latin America", "North America"], + datasets: [{ + label: "Population (millions)", + backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"], + data: [2478,5267,734,784,433] + }] + }, + /*options: { + title: { + display: true, + text: 'Predicted world population (millions) in 2050' + } + }*/ + }); + } + +} diff --git a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html index 43a2d766..413aa6f3 100644 --- a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html @@ -1 +1 @@ -<p>pie-chart works!</p> +<canvas #piechart width="800" height="450"></canvas> 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 dde5cbab..e7d79615 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 @@ -1,15 +1,36 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'node_modules/chart.js'; @Component({ selector: 'app-pie-chart', templateUrl: './pie-chart.component.html', styleUrls: ['./pie-chart.component.css'] }) -export class PieChartComponent implements OnInit { +export class PieChartComponent implements AfterViewInit { + @ViewChild('piechart') chartRef!: ElementRef; constructor() { } - ngOnInit(): void { + ngAfterViewInit(): void { + const myChart = new Chart(this.chartRef.nativeElement, { + type: 'pie', + data: { + labels: ["Africa", "Asia", "Europe", "Latin America", "North America"], + datasets: [{ + label: "Population (millions)", + backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"], + data: [2478,5267,734,784,433] + }] + }, + /*options: { + title: { + display: true, + text: 'Predicted world population (millions) in 2050' + } + }*/ +}); + } + } diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css index cfb0d087..3e865576 100644 --- a/frontend/src/app/_elements/folder/folder.component.css +++ b/frontend/src/app/_elements/folder/folder.component.css @@ -1,7 +1,7 @@ #folder { - position: absolute; + /*position: absolute; left: 50%; - transform: translateX(-50%); + transform: translateX(-50%);*/ } #tabs { @@ -22,9 +22,10 @@ align-items: center; position: relative; overflow-x: hidden; - background-color: var(--ns-bg-dark-100); height: 2.5rem; + background-color: var(--ns-bg-dark-100); border-color: var(--ns-primary); + color: var(--ns-primary); border-style: solid; border-width: 1px 1px 0 1px; } @@ -37,6 +38,7 @@ .selected-tab { height: 3rem; background-color: var(--ns-primary); + color: var(--offwhite); } .hover-tab { @@ -55,7 +57,7 @@ } .tab-link:active { - color: var(--ns-accent) !important; + text-decoration: underline !important; } .selected-tab { @@ -91,7 +93,7 @@ background-color: var(--ns-bg-dark-50); width: 100%; height: 36rem; - backdrop-filter: blur(2px); + /*backdrop-filter: blur(2px);*/ border-color: var(--ns-primary); border-style: solid; border-width: 1px 1px 1px 1px; @@ -104,21 +106,18 @@ justify-content: center; } -.folder-bottom-button { +.bottom-button { font-size: large; position: relative; - background-color: var(--ns-bg-dark-100); + background-color: var(--ns-primary); width: 10rem; - height: 2.5rem; - display: flex; - flex-direction: row; - justify-content: space-around; - align-items: center; + height: 2.3rem; border-color: var(--ns-primary); border-style: solid; border-width: 0px 1px 1px 1px; } -.folder-bottom-button:hover { - background-color: var(--ns-primary); +.rounded-bottom { + border-top-right-radius: 0; + border-top-left-radius: 0; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html index 11fccc56..c3da30fc 100644 --- a/frontend/src/app/_elements/folder/folder.component.html +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -1,12 +1,12 @@ <div id="folder" style="width: 60rem;"> - <div id="tabs" class="text-offwhite"> + <div id="tabs"> <div id="new-file-tab" class="folder-tab p-1 rounded-top" [style]="'z-index:' + newFileZIndex() + ' ;'" [ngClass]="{'selected-tab' : newFileSelected, 'hover-tab' : hoveringOverFileIndex == -2}"> <mat-icon class="text-offwhite">add</mat-icon> <a class="stretched-link tab-link" (click)="selectNewFile()" (mouseenter)="hoverOverFile(-2)" (mouseleave)="hoverOverFile(-1)"> <p class="m-1" *ngIf="newFile != undefined">{{newFile.name}}</p> </a> </div> - <div class="folder-tab p-1 rounded-top" *ngFor="let file of files; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedFileIndex == i, 'hover-tab' : hoveringOverFileIndex == i}"> + <div class="folder-tab p-1 rounded-top" *ngFor="let file of filteredFiles; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedFileIndex == i, 'hover-tab' : hoveringOverFileIndex == i}"> <a class="m-1 stretched-link tab-link" (click)="selectFile(i)" (mouseenter)="hoverOverFile(i)" (mouseleave)="hoverOverFile(-1)">{{file.name}}</a> </div> </div> @@ -15,27 +15,22 @@ <div id="path" class="ps-2">{{folderName}} </div> <mat-icon>keyboard_arrow_right</mat-icon> - <div id="search"> + <div id="search" class="text-offwhite"> <mat-form-field> - <input matNativeControl> + <button matPrefix class="btn-clear input-icon"><mat-icon>search</mat-icon></button> + <input type="search" matInput name="search" [(ngModel)]="searchTerm" (input)="searchTermsChanged()"> + <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"> - <mat-chip-list aria-label="filter selection"> - <mat-chip class="text-offwhite ns-bg-dark-50"> - <mat-icon class="text-offwhite">timeline</mat-icon> - Regresioni - </mat-chip> - <mat-chip class="text-offwhite ns-bg-dark-50"> - <mat-icon class="text-offwhite">looks_two</mat-icon> - Binarni klasifikacioni - </mat-chip> - <mat-chip class="text-offwhite ns-bg-dark-50"> - <mat-icon class="text-offwhite">auto_awesome_motion</mat-icon> - Multiklasifikacioni - </mat-chip> - </mat-chip-list> + + <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> <a class="tab-link p-1" data-bs-toggle="collapse" data-bs-target="#collapseFilters" aria-expanded="false" aria-controls="collapseFilters"> <mat-icon>filter_alt</mat-icon> @@ -50,19 +45,23 @@ </div> {{fileToDisplay ? fileToDisplay.name : 'No file selected.'}} {{selectedFileIndex}} {{hoveringOverFileIndex}} </div> - <div id="footer"> - <div class="folder-bottom-button text-offwhite rounded-bottom" *ngIf="newFileSelected"> - <a class="tab-link stretched-link">Sačuvaj</a> - <div> - <mat-icon>check</mat-icon> + <div id="footer" [ngSwitch]="newFileSelected"> + <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="true"> + <div class="f-row"> + <div>Sačuvaj</div> + <div class="pt-1"> + <mat-icon>check</mat-icon> + </div> </div> - </div> - <div class="folder-bottom-button text-offwhite rounded-bottom" *ngIf="!newFileSelected"> - <a class="tab-link stretched-link">Ok</a> - <div class="icon-double"> - <mat-icon>check</mat-icon> - <mat-icon>check</mat-icon> + </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> + </div> </div> - </div> + </button> </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 34c8db82..91565f3c 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -20,11 +20,15 @@ export class FolderComponent implements OnInit { newFileSelected: boolean = true; selectedFileIndex: number = -1; + selectedFile?: (Dataset | Model); hoveringOverFileIndex: number = -1; fileToDisplay?: (Dataset | Model); @Output() selectedFileChanged: EventEmitter<(Dataset | Model)> = new EventEmitter(); + @Output() okPressed: EventEmitter<string> = new EventEmitter(); + + searchTerm: string = ''; constructor() { //PLACEHOLDER @@ -33,6 +37,9 @@ export class FolderComponent implements OnInit { new Dataset('Dijamanti'), new Dataset('Filmovi'), ] + + this.filteredFiles.length = 0; + this.filteredFiles.push(...this.files); } ngOnInit(): void { @@ -60,17 +67,19 @@ export class FolderComponent implements OnInit { if (!this.newFile) { this.createNewFile(); } - this.fileToDisplay = this.newFile; this.selectedFileIndex = -1; + this.fileToDisplay = this.newFile; + this.selectedFile = this.newFile; this.newFileSelected = true; this.selectedFileChanged.emit(this.newFile); } selectFile(index: number) { this.selectedFileIndex = index; - this.fileToDisplay = this.files[index]; + this.selectedFile = this.filteredFiles[index]; + this.fileToDisplay = this.filteredFiles[index]; this.newFileSelected = false; - this.selectedFileChanged.emit(this.files[index]); + this.selectedFileChanged.emit(this.selectedFile); } createNewFile() { @@ -81,6 +90,10 @@ export class FolderComponent implements OnInit { } } + ok() { + this.okPressed.emit(); + } + saveNewFile() { // TODO } @@ -98,6 +111,23 @@ export class FolderComponent implements OnInit { return (this.files.length + 1); } + clearSearchTerm() { + this.searchTerm = ''; + } + + filteredFiles: (Dataset | Model)[] = []; + + searchTermsChanged() { + this.filteredFiles.length = 0; + this.filteredFiles.push(...this.files.filter((file) => file.name.toLowerCase().includes(this.searchTerm.toLowerCase()))); + if (this.selectedFile) { + if (!this.filteredFiles.includes(this.selectedFile)) { + this.selectFile(-1); + } else { + this.selectedFileIndex = this.filteredFiles.indexOf(this.selectedFile); + } + } + } } export enum FolderType { diff --git a/frontend/src/app/_elements/form-model/form-model.component.css b/frontend/src/app/_elements/form-model/form-model.component.css index 9340fed5..f4d085ea 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.css +++ b/frontend/src/app/_elements/form-model/form-model.component.css @@ -1,3 +1,50 @@ -*{ +#container{ + color:var(--offwhite); +} +mat-label{ color: var(--offwhite) !important; +} +select{ + color: var(--offwhite) !important; +} +mat-form-field{ + color: var(--offwhite) !important; + padding: 0; +} +hr{ + color: var(--offwhite) !important; + margin-bottom: 30px;; +} +.row{ + margin: 0; + padding: 0; +} +mat-icon{ + color: var(--ns-primary); +} +#rowhn{ + margin-bottom:-50px; + padding: 0; +} +.neuron{ + + text-align: justify; + border: 1px solid white; + border-radius: 5px; + padding: 0; + color: white!important; + background-color: var(--ns-bg-dark-100) !important; + min-width: none; + max-width: 12.5rem; + +} +mat-form-field{ + font-size: 12px; +} +col-1{ + text-align: center; +} +mat-icon{ + margin-right: 5px; + margin-left: -7px; }
\ No newline at end of file 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 5db2cb49..0b63c5ac 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -1,31 +1,29 @@ -<div class="container"> +<div id="container"> <div class="row"> <div class="col-sm"> <div class="row"> - <mat-form-field appearance="fill"> - <mat-label>Naziv</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option label="--select something --"></option> - <option value="saab">Saab</option> - <option value="mercedes">Mercedes</option> - <option value="audi">Audi</option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> - Obavezno polje - </mat-error> - </mat-form-field> + <mat-form-field class="example-full-width" appearance="fill"> + <mat-label>Naziv</mat-label> + <input type="text" matInput [formControl]="nameFormControl"> + <mat-error *ngIf="nameFormControl.hasError('name') && !nameFormControl.hasError('required')"> + Unesite naziv + </mat-error> + <mat-error *ngIf="nameFormControl.hasError('required')"> + Naziv je <strong>obavezan</strong> + </mat-error> + </mat-form-field> </div> <div class="row"> <mat-form-field appearance="fill"> <mat-label>Tip problema</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option + <mat-select matNativeControl required [formControl]="selectTypeFormControl"> + <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> {{ optionName }} - </option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> + </mat-option> + </mat-select> + <mat-error *ngIf="selectTypeFormControl.hasError('required')"> Obavezno polje </mat-error> </mat-form-field> @@ -35,105 +33,152 @@ <div class="row"> <mat-form-field appearance="fill"> <mat-label>Optimizacija</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option + <mat-select matNativeControl required [formControl]="selectOptFormControl"> + <mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> {{ optionName }} - </option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> + </mat-option> + </mat-select> + <mat-error *ngIf="selectOptFormControl.hasError('required')"> Obavezno polje </mat-error> </mat-form-field> </div> <div class="row"> - <mat-form-field appearance="fill"> - <mat-label>Learning rate</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option label="--select something --"></option> - <option value="saab">Saab</option> - <option value="mercedes">Mercedes</option> - <option value="audi">Audi</option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> - Obavezno polje + <mat-form-field appearance="fill"> + <mat-label>Funkcija troška</mat-label> + <mat-select matNativeControl required [formControl]="selectLFFormControl"> + <mat-option + *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" + [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + <mat-error *ngIf="selectLFFormControl.hasError('required')"> + Obavezno polje </mat-error> </mat-form-field> </div> </div> <div class="col-sm"> <div class="row"> - <mat-form-field appearance="fill"> - <mat-label>Broj epoha</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option label="--select something --"></option> - <option value="saab">Saab</option> - <option value="mercedes">Mercedes</option> - <option value="audi">Audi</option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> - Obavezno polje - </mat-error> - </mat-form-field> + + <mat-form-field appearance="fill"> + <mat-label>Funkcija aktivacije izlaznog sloja</mat-label> + <mat-select matNativeControl required [formControl]="selectAFFormControl" name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <mat-option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + <mat-error *ngIf="selectAFFormControl.hasError('required')"> + Obavezno polje + </mat-error> + </mat-form-field> + </div> <div class="row"> - <mat-form-field appearance="fill"> - <mat-label>Broj uzoraka po iteraciji</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option label="--select something --"></option> - <option value="saab">Saab</option> - <option value="mercedes">Mercedes</option> - <option value="audi">Audi</option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> - Obavezno polje + <mat-form-field appearance="fill"> + <mat-label>Funkcija troška</mat-label> + <mat-select matNativeControl required [formControl]="selectLFFormControl"> + <mat-option + *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" + [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + <mat-error *ngIf="selectLFFormControl.hasError('required')"> + Obavezno polje </mat-error> </mat-form-field> </div> </div> - <div class="col-sm"> + <div class="col"> + <div class="row"> + <div class="col-7">Broj Epoha</div> + <mat-icon (click)="addEpoch()">add_circle</mat-icon> + <div class="col-1">{{newModel.epochs}}</div> + <mat-icon (click)="removeEpoch()">remove_circle</mat-icon> + </div> + <br> + <br> <div class="row"> + <div class="col-7">Broj Uzoraka Po Iteraciji</div> + <mat-icon (click)="addBatch()">add_circle</mat-icon> + <div class="col-1">{{newModel.batchSize}}</div> + <mat-icon (click)="removeBatch()">remove_circle</mat-icon> + </div> + </div> + </div><!--kraj unosa parametara--> + <hr> + <div class="m-5"> + <app-graph [model]="newModel" [inputCols]="newModel.inputColNum"></app-graph> + <div class="row" id="rowhn"> + <div class="col-3"></div> + <div class="col-2">Broj Skrivenih Slojeva</div> + <div class="col-1"><mat-icon (click)="addLayer()" (ngModelChange)="updateGraph()">add_circle</mat-icon></div> + <div class="col-1">{{newModel.hiddenLayers}}</div> + <div class="col-1"><mat-icon (click)="removeLayer()" (ngModelChange)="updateGraph()">remove_circle</mat-icon></div> + </div> + </div> + <hr> + <div class="row" style="max-width:60rem ;"> + + <div class="col text-center" *ngFor="let item of numSequence(newModel.hiddenLayers)" > + {{item}} + <div class="neuron"> + <div style="text-align: center;"> + <label >Skriveni sloj</label> + </div> + <div class="row" style="margin-bottom: -10px;"> <mat-form-field appearance="fill"> - <mat-label>Funkcija aktivacije izlaznog sloja</mat-label> - <select matNativeControl required [formControl]="selectFormControl" name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> - <option - *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> + <mat-label>Aktivaciona funkcija</mat-label> + <mat-select matNativeControl required [formControl]="selectActivationFormControl"> + <mat-option value="saab">Relu</mat-option> + <mat-option value="mercedes">Sigmoid</mat-option> + <mat-option value="audi">Softmax</mat-option> + </mat-select> + <mat-error *ngIf="selectActivationFormControl.hasError('required')"> Obavezno polje </mat-error> </mat-form-field> + </div> + <div class="row" > + <div class="col-6" style="font-size: 13px;" >Broj čvorova</div> + <mat-icon (click)="addNeuron()">add_circle</mat-icon> + <div class="col-1">{{newModel.hiddenLayerNeurons}}</div> + <mat-icon (click)="removeNeuron()">remove_circle</mat-icon> + </div> - <div class="row"> - <mat-form-field appearance="fill"> - <mat-label>Funkcija troška</mat-label> - <select matNativeControl required [formControl]="selectFormControl"> - <option - *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" - [value]="option"> - {{ optionName }} - </option> - </select> - <mat-error *ngIf="selectFormControl.hasError('required')"> - Obavezno polje + <div class='row' style="margin-bottom: -7px;"> + <mat-form-field appearance="fill"> + <mat-label>Regularizacija</mat-label> + <mat-select matNativeControl required [formControl]="selectRegularisationFormControl"> + <mat-option value="l1">L1</mat-option> + <mat-option value="l2">L2</mat-option> + </mat-select> + <mat-error *ngIf="selectRegularisationFormControl.hasError('required')"> + Obavezno polje + </mat-error> + </mat-form-field> + </div> + <div class="row" style="margin-bottom: -7px;"> + <mat-form-field appearance="fill"> + <mat-label>Stopa regularizacije</mat-label> + <mat-select matNativeControl required [formControl]="selectRRateFormControl"> + <mat-option value="saab">0.001</mat-option> + <mat-option value="mercedes">0.01</mat-option> + <mat-option value="audi">0.1</mat-option> + </mat-select> + <mat-error *ngIf="selectRRateFormControl.hasError('required')"> + Obavezno polje </mat-error> </mat-form-field> + </div> </div> + <br> </div> - </div><!--kraj unosa parametara--> - - <div class="col-1"> - <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> - </div> - <div class="m-5"> - <app-graph [model]="newModel" [inputCols]="4"></app-graph> - <div class="col-1"> - <input type="number" min="1" class="form-control" name="hiddenLayers" [(ngModel)]="newModel.hiddenLayers" (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])" (ngModelChange)="updateGraph()"> - </div> - </div> - + </div>
\ No newline at end of file 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 6f795161..b1d0a2a9 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -4,6 +4,8 @@ import Shared from 'src/app/Shared'; import Experiment from 'src/app/_data/Experiment'; import Model, { ActivationFunction, LossFunction, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType } from 'src/app/_data/Model'; import { GraphComponent } from '../graph/graph.component'; +import {FormGroupDirective, NgForm} from '@angular/forms'; +import {ErrorStateMatcher} from '@angular/material/core'; @Component({ selector: 'app-form-model', templateUrl: './form-model.component.html', @@ -14,19 +16,25 @@ export class FormModelComponent implements OnInit { @Input() forExperiment?: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); - constructor() { } + constructor() { + this.newModel.epochs=1; + this.newModel.batchSize=1; +} ngOnInit(): void { } - animalControl = new FormControl('', Validators.required); selectFormControl = new FormControl('', Validators.required); - /*animals: Animal[] = [ - {name: 'Dog', sound: 'Woof!'}, - {name: 'Cat', sound: 'Meow!'}, - {name: 'Cow', sound: 'Moo!'}, - {name: 'Fox', sound: 'Wa-pa-pa-pa-pa-pa-pow!'}, - ]; - */ + nameFormControl = new FormControl('', [Validators.required, Validators.email]); + selectTypeFormControl=new FormControl('', Validators.required); + selectOptFormControl=new FormControl('', Validators.required); + selectLFFormControl=new FormControl('', Validators.required); + selectLRFormControl=new FormControl('', Validators.required); + selectEpochFormControl=new FormControl('', Validators.required); + selectAFFormControl=new FormControl('', Validators.required); + selectBSFormControl=new FormControl('', Validators.required); + selectActivationFormControl = new FormControl('', Validators.required); + selectRegularisationFormControl = new FormControl('', Validators.required); + selectRRateFormControl = new FormControl('', Validators.required); newModel: Model = new Model(); myModels?: Model[]; selectedModel?: Model; @@ -45,10 +53,115 @@ export class FormModelComponent implements OnInit { lossFunction: any = LossFunction; showMyModels: boolean = true; + + hiddenLayers=[]; + + - batchSizePower: number = 2; updateGraph() { this.graph.update(); } + removeLayer(){ + if(this.newModel.hiddenLayers>1) + { + this.newModel.hiddenLayers-=1; + this.updateGraph(); + } + else + { + this.newModel.hiddenLayers=this.newModel.hiddenLayers; + } + + } + addLayer(){ + if(this.newModel.hiddenLayers<12) + { + this.newModel.hiddenLayers+=1; + this.updateGraph(); + } + else + { + this.newModel.hiddenLayers=this.newModel.hiddenLayers; + + } + } + removeBatch(){ + if(this.newModel.batchSize>1) + { + this.newModel.batchSize=this.newModel.batchSize/2; + } + else + { + this.newModel.batchSize=this.newModel.batchSize; + } + + } + addBatch(){ + if(this.newModel.batchSize<600) + { + this.newModel.batchSize=this.newModel.batchSize*2; + } + else + { + this.newModel.batchSize=this.newModel.batchSize; + + } + } + removeEpoch(){ + if(this.newModel.epochs>1) + { + this.newModel.epochs=this.newModel.epochs-1; + } + else + { + this.newModel.epochs=this.newModel.epochs; + } + + } + addEpoch(){ + if(this.newModel.epochs<100) + { + this.newModel.epochs=this.newModel.epochs+1; + } + else + { + this.newModel.epochs=this.newModel.epochs; + + } + } + /* + setNeurons() + { + for(let i=0;i<this.newModel.hiddenLayers;i++){ + this.newModel.hiddenLayerNeurons[i]=1; + } + }*/ + numSequence(n: number): Array<number> { + return Array(n); + } + removeNeuron(){ + if(this.newModel.hiddenLayerNeurons>1) + { + this.newModel.hiddenLayerNeurons=this.newModel.hiddenLayerNeurons-1; + this.updateGraph(); + } + else + { + this.newModel.hiddenLayerNeurons=this.newModel.hiddenLayerNeurons; + } + + } + addNeuron(){ + if(this.newModel.hiddenLayerNeurons<100) + { + this.newModel.hiddenLayerNeurons=this.newModel.hiddenLayerNeurons+1; + this.updateGraph(); + } + else + { + this.newModel.hiddenLayerNeurons=this.newModel.hiddenLayerNeurons; + + } + } } diff --git a/frontend/src/app/_elements/navbar/navbar.component.html b/frontend/src/app/_elements/navbar/navbar.component.html index 5390136d..09d83bd1 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.html +++ b/frontend/src/app/_elements/navbar/navbar.component.html @@ -1,4 +1,4 @@ -<header class="sticky-top p-3 bg-dark text-white"> +<header class="bg-dark text-white"> <div class="container"> <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start"> <a routerLink="" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none"> diff --git a/frontend/src/app/_elements/reactive-background/reactive-background.component.ts b/frontend/src/app/_elements/reactive-background/reactive-background.component.ts index 97387ac8..1a6157e3 100644 --- a/frontend/src/app/_elements/reactive-background/reactive-background.component.ts +++ b/frontend/src/app/_elements/reactive-background/reactive-background.component.ts @@ -1,5 +1,6 @@ import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { CookieService } from 'ngx-cookie-service'; +import Shared from 'src/app/Shared'; @Component({ selector: 'app-reactive-background', @@ -90,18 +91,32 @@ export class ReactiveBackgroundComponent implements AfterViewInit { this.drawBackground(); }, 1000 / 60); + + Shared.bgScroll.subscribe((amount) => { + this.scrollBackgroundFromSharedEvent(amount); + }) } private lastScrollY: number = 0; + scrollBackgroundFromSharedEvent(amount: number) { + const scrolledAmount = amount - this.lastScrollY; + this.scrollPoints(scrolledAmount); + this.lastScrollY = amount; + } + scrollBackground(e: Event) { const scrolledAmount = window.scrollY - this.lastScrollY; + this.scrollPoints(scrolledAmount); + this.lastScrollY = window.scrollY; + } + + scrollPoints(amount: number) { this.points.forEach((point, index) => { if (index > this.numPoints * this.fill) return; - point.y = point.y - (scrolledAmount / this.height) * this.scrollSpeed; + point.y = point.y - (amount / this.height) * this.scrollSpeed; this.keepPointWithinBounds(point); }) - this.lastScrollY = window.scrollY; } drawBackground() { diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css index 34e412ef..2fde8e7f 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.css +++ b/frontend/src/app/_pages/experiment/experiment.component.css @@ -1,37 +1,49 @@ -ul{ +ul { list-style: none; } -.navmenu{ - position: absolute; - background-color: var(--ns-bg-dark-50); - height: 90%; - width: 250px; + +.holder { + display: flex; + flex-direction: row; + align-items: stretch; } -.navmenuwrapper{ - position: relative; - top:40%; - transform: translateY(-50%); + +.sidenav { + width: 250px; + background-color: var(--ns-bg-dark-50); } +@media only screen and (max-width: 400px) { + .sidenav { + width: 100%; + background-color: var(--ns-bg-dark-100); + } + .holder { + flex-direction: column; + } +} -mat-stepper{ +mat-stepper { background-color: transparent; } -::ng-deep .mat-step-header .mat-step-icon .mat-step-content{ - background-color: white; - color:var(--ns-primary); - } -::ng-deep .mat-step-header .mat-step-icon{ - color:white; -} -::ng-deep .mat-step-header .mat-step-icon-state-done{ - background: var(--ns-primary); - color:var(--ns-primary) +.label { + color: white; } -::ng-deep .mat-step-header .mat-step-icon-state-done .label{ - color:var(--ns-primary) + +.steps-container { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + overflow-y: auto; } -.label{ - color: white; + +.step-content { + position: relative; + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; }
\ No newline at end of file diff --git a/frontend/src/app/_pages/experiment/experiment.component.html b/frontend/src/app/_pages/experiment/experiment.component.html index e0746d4a..c988a50a 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.html +++ b/frontend/src/app/_pages/experiment/experiment.component.html @@ -1,37 +1,35 @@ -<app-folder></app-folder> -<div class="wrapper"> - - <nav class="navmenu"> - <div class="navmenuwrapper text-offwhite"> - <mat-stepper orientation="vertical" (selectionChange)="test($event)"> +<div class="container-fluid p-0 text-offwhite holder" style="height: calc(100vh - 64px);"> + <div class="d-flex flex-colum align-items-center sidenav"> + <mat-stepper orientation="vertical" (selectionChange)="changePage($event)"> <mat-step> - <ng-template matStepLabel><span class="label">Izvor podataka</span></ng-template> - <ng-template matStepContent> - <p>Izaberite vas izvor podataka</p> - </ng-template> + <!--editable="false"--> + <ng-template matStepLabel><span class="label">Izvor podataka</span></ng-template> + <ng-template matStepContent> + <p>Izaberite vas izvor podataka</p> + </ng-template> </mat-step> <mat-step> - <ng-template matStepLabel> <span class="label">Odabir kolona</span></ng-template> - <ng-template matStepContent> - <p>Pripremite podatke i izaberite izlazne kolone</p> - </ng-template> + <ng-template matStepLabel> <span class="label">Odabir kolona</span></ng-template> + <ng-template matStepContent> + <p>Pripremite podatke i izaberite izlazne kolone</p> + </ng-template> </mat-step> - <mat-step > - <ng-template matStepLabel><span class="label">Treniranje</span></ng-template> - <p>Odaberite parametre i trenirajte model</p> + <mat-step> + <ng-template matStepLabel><span class="label">Treniranje</span></ng-template> + <p>Odaberite parametre i trenirajte model</p> </mat-step> - </mat-stepper> + </mat-stepper> + </div> + <div #stepsContainer class="steps-container"> + <div #steps id="step_1" class="step-content"> + <app-folder (okPressed)="goToPage(1)"></app-folder> + </div> + <div #steps id="step_2" class="step-content"> + <div class="text-offwhite" style="height: 100px;width: 100px;background-color: red;top:50%;left: 50%;position: absolute;">Insert odabir kolona</div> + </div> + <div #steps id="step_3" class="step-content"> + + <app-form-model></app-form-model> </div> - - - - </nav> - - - - - -</div> - - - + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/experiment/experiment.component.ts b/frontend/src/app/_pages/experiment/experiment.component.ts index f06a7c48..ad0f1df2 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.ts +++ b/frontend/src/app/_pages/experiment/experiment.component.ts @@ -1,19 +1,89 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren } from '@angular/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { MatStepper } from '@angular/material/stepper'; +import Shared from 'src/app/Shared'; + @Component({ selector: 'app-experiment', templateUrl: './experiment.component.html', styleUrls: ['./experiment.component.css'] }) -export class ExperimentComponent implements OnInit { +export class ExperimentComponent implements AfterViewInit { + + @ViewChild(MatStepper) stepper!: MatStepper; + @ViewChild('stepsContainer') stepsContainer!: ElementRef; + @ViewChildren('steps') steps!: ElementRef[]; + + event: number = 0; constructor() { } - ngOnInit(): void { + stepHeight = this.calcStepHeight(); + + calcStepHeight() { + return window.innerHeight - 64; + } + + ngAfterViewInit(): void { + window.addEventListener('resize', () => { + this.updatePageHeight(); + }) + this.updatePageHeight(); + + setInterval(() => { + this.updatePageIfScrolled(); + }, 100); + + this.stepsContainer.nativeElement.addEventListener('scroll', (event: Event) => { + Shared.emitBGScrollEvent(this.stepsContainer.nativeElement.scrollTop); + }); + } + + updatePageIfScrolled() { + if (this.scrolling) return; + const currentPage = Math.round(this.stepsContainer.nativeElement.scrollTop / this.stepHeight) + this.stepper.selectedIndex = currentPage; + } + + updatePageHeight() { + this.stepHeight = this.calcStepHeight(); + const stepHeight = `${this.stepHeight}px`; + this.stepsContainer.nativeElement.style.minHeight = stepHeight; + this.steps.forEach(step => { + step.nativeElement.style.minHeight = stepHeight; + }) + } + + changePage(event: StepperSelectionEvent) { + this.updatePage(<number>event.selectedIndex) } - test(event:StepperSelectionEvent){ - console.log(event); + goToPage(pageNum: number) { + this.stepper.selectedIndex = pageNum; + this.updatePage(pageNum); } + scrollTimeout: any; + + updatePage(pageNum: number) { + this.scrolling = true; + this.event = pageNum; + let scrollAmount = 0; + this.steps.forEach((step, index) => { + if (index == pageNum) { + scrollAmount = step.nativeElement.offsetTop; + } + }) + clearTimeout(this.scrollTimeout); + this.scrollTimeout = setTimeout(() => { + this.scrolling = false; + }, 800); + this.stepsContainer.nativeElement.scroll({ + top: scrollAmount, + behavior: 'smooth' //auto, smooth, initial, inherit + }); + } + + scrolling: boolean = false; + } diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html index f73e7571..e682d8dd 100644 --- a/frontend/src/app/_pages/home/home.component.html +++ b/frontend/src/app/_pages/home/home.component.html @@ -16,4 +16,5 @@ </div> + </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/test/test.component.css b/frontend/src/app/_pages/test/test.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/test/test.component.css diff --git a/frontend/src/app/_pages/test/test.component.html b/frontend/src/app/_pages/test/test.component.html new file mode 100644 index 00000000..625739f8 --- /dev/null +++ b/frontend/src/app/_pages/test/test.component.html @@ -0,0 +1,3 @@ +<app-pie-chart></app-pie-chart> +<app-doughnut-chart></app-doughnut-chart> +<app-barchart></app-barchart>
\ No newline at end of file diff --git a/frontend/src/app/_pages/test/test.component.spec.ts b/frontend/src/app/_pages/test/test.component.spec.ts new file mode 100644 index 00000000..e0f9bcc9 --- /dev/null +++ b/frontend/src/app/_pages/test/test.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestComponent } from './test.component'; + +describe('TestComponent', () => { + let component: TestComponent; + let fixture: ComponentFixture<TestComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ TestComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/test/test.component.ts b/frontend/src/app/_pages/test/test.component.ts new file mode 100644 index 00000000..b3c0d8cf --- /dev/null +++ b/frontend/src/app/_pages/test/test.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + styleUrls: ['./test.component.css'] +}) +export class TestComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 30cf2ea8..f5f1ccae 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { PlaygroundComponent } from './_pages/playground/playground.component'; import { ExperimentComponent } from './_pages/experiment/experiment.component'; import { ArchiveComponent } from './_pages/archive/archive.component'; import { ColumnTableComponent } from './_elements/column-table/column-table.component'; +import { TestComponent } from './_pages/test/test.component'; const routes: Routes = [ { path: '', component: HomeComponent, data: { title: 'Početna strana' } }, @@ -15,7 +16,8 @@ const routes: Routes = [ { path: 'archive', component: ArchiveComponent, data: { title: 'Arhiva' } }, { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } }, { path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } }, - { path: 'sonja', component: ColumnTableComponent } + { path: 'sonja', component: ColumnTableComponent }, + { path: 'test', component: TestComponent, data: { title: 'Test' } } ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index 8c3f4e9e..d15793e7 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -6,10 +6,8 @@ <app-reactive-background [speed]="0.001" [scrollSpeed]="1" [minDistance]="0.14" [maxSize]="5" [cursorDistance]="0.22" lineColor="#00a8e8" pointColor="rgba(0, 188, 252, 1)" cursorLineColor="#00a8e8" [numPoints]="100"> </app-reactive-background> <app-navbar></app-navbar> -<a class="bg-controls" routerLink="playground"> +<a class="bg-controls" style="z-index: 1000;" routerLink="playground"> <mat-icon color="accent">settings_suggest</mat-icon> </a> -<div class="h-100"> - <router-outlet></router-outlet> -</div> +<router-outlet></router-outlet> <app-notifications></app-notifications>
\ No newline at end of file diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 4a0d85c8..f9bc2726 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { AfterViewInit, Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router, NavigationEnd, ActivatedRoute } from '@angular/router'; import { filter, map } from 'rxjs'; @@ -11,10 +11,12 @@ import Shared from './Shared'; templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent implements OnInit { +export class AppComponent implements OnInit, AfterViewInit { constructor(private router: Router, private titleService: Title, private authService: AuthService, private signalRService: SignalRService, private http: HttpClient) { } + ngAfterViewInit(): void { + } ngOnInit() { this.router.events @@ -42,17 +44,5 @@ export class AppComponent implements OnInit { } this.signalRService.startConnection(); //this.startHttpRequest(); - - - - - } - private startHttpRequest = () => { - this.http.get('http://localhost:5283/chatHub') - .subscribe(res => { - console.log(res); - }) } - - } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 70e78394..c46381d5 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -45,6 +45,8 @@ import { FormDatasetComponent } from './_elements/form-dataset/form-dataset.comp import { FormModelComponent } from './_elements/form-model/form-model.component'; import { ColumnTableComponent } from './_elements/column-table/column-table.component'; import { FolderComponent } from './_elements/folder/folder.component'; +import { TestComponent } from './_pages/test/test.component'; +import { DoughnutChartComponent } from './_elements/_charts/doughnut-chart/doughnut-chart.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); @@ -78,7 +80,9 @@ export function initializeApp(appConfig: Configuration) { BoxPlotComponent, FolderComponent, EncodingDialogComponent, - MissingvaluesDialogComponent + MissingvaluesDialogComponent, + TestComponent, + DoughnutChartComponent, ], imports: [ BrowserModule, diff --git a/frontend/src/custom-theme.scss b/frontend/src/custom-theme.scss index a6538c37..8f716240 100644 --- a/frontend/src/custom-theme.scss +++ b/frontend/src/custom-theme.scss @@ -1,35 +1,250 @@ +/** +* Generated theme by Material Theme Generator +* https://materialtheme.arcsine.dev +* Fork at: https://materialtheme.arcsine.dev/?c=YHBhbGV0dGU$YHByaW1hcnk$YF48IzAwNjNhYiIsIj9lcjwjYjNkMGU2IiwiO2VyPCMwMDQ3OTF$LCIlPmBePCMwMGE4ZTgiLCI~ZXI8I2IzZTVmOCIsIjtlcjwjMDA4ZGRlfiwid2Fybj5gXjwjZjliN2I3IiwiP2VyPCNmZGU5ZTkiLCI7ZXI8I2Y2OWY5Zn4sIj9UZXh0PCMwMDQxNjUiLCI~PTwjZGZkN2Q3IiwiO1RleHQ8I2RmZDdkNyIsIjs9PCMwMDQxNjV$LCJmb250cz5bYEA8KC00fixgQDwoLTN$LGBAPCgtMn4sYEA8KC0xfixgQDxoZWFkbGluZX4sYEA8dGl0bGV$LGBAPHN1YiktMn4sYEA8c3ViKS0xfixgQDxib2R5LTJ$LGBAPGJvZHktMX4sYEA8YnV0dG9ufixgQDxjYXB0aW9ufixgQDxpbnB1dCIsInNpemU$bnVsbH1dLCJpY29uczxGaWxsZWQiLCI~bmVzcz5mYWxzZSwidmVyc2lvbj4xM30= +*/ -// Custom Theming for Angular Material -// For more information: https://material.angular.io/guide/theming @use '@angular/material' as mat; -// Plus imports for other components in your app. - // Include the common styles for Angular Material. We include this here so that you only // have to load a single css file for Angular Material in your app. -// Be sure that you only ever include this mixin once! -@include mat.core(); +// Fonts +@import 'https://fonts.googleapis.com/icon?family=Material+Icons'; +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap'); +$fontConfig: ( display-4: mat.define-typography-level(112px, 112px, 300, 'Roboto', -0.0134em), +display-3: mat.define-typography-level(56px, 56px, 400, 'Roboto', -0.0089em), +display-2: mat.define-typography-level(45px, 48px, 400, 'Roboto', 0.0000em), +display-1: mat.define-typography-level(34px, 40px, 400, 'Roboto', 0.0074em), +headline: mat.define-typography-level(24px, 32px, 400, 'Roboto', 0.0000em), +title: mat.define-typography-level(20px, 32px, 500, 'Roboto', 0.0075em), +subheading-2: mat.define-typography-level(16px, 28px, 400, 'Roboto', 0.0094em), +subheading-1: mat.define-typography-level(15px, 24px, 500, 'Roboto', 0.0067em), +body-2: mat.define-typography-level(14px, 24px, 500, 'Roboto', 0.0179em), +body-1: mat.define-typography-level(14px, 20px, 400, 'Roboto', 0.0179em), +button: mat.define-typography-level(14px, 14px, 500, 'Roboto', 0.0893em), +caption: mat.define-typography-level(12px, 20px, 400, 'Roboto', 0.0333em), +input: mat.define-typography-level(inherit, 1.125, 400, 'Roboto', 1.5px)); +// Foreground Elements +// Light Theme Text +$dark-text: #004165; +$dark-primary-text: rgba($dark-text, +0.87); +$dark-accent-text: rgba($dark-primary-text, +0.54); +$dark-disabled-text: rgba($dark-primary-text, +0.38); +$dark-dividers: rgba($dark-primary-text, +0.12); +$dark-focused: rgba($dark-primary-text, +0.12); +$mat-light-theme-foreground: ( base : black, +divider : $dark-dividers, +dividers : $dark-dividers, +disabled : $dark-disabled-text, +disabled-button : rgba($dark-text, 0.26), +disabled-text : $dark-disabled-text, +elevation : black, +secondary-text : $dark-accent-text, +hint-text : $dark-disabled-text, +accent-text : $dark-accent-text, +icon : $dark-accent-text, +icons : $dark-accent-text, +text : $dark-primary-text, +slider-min : $dark-primary-text, +slider-off : rgba($dark-text, 0.26), +slider-off-active: $dark-disabled-text, +); +// Dark Theme text +$light-text: #dfd7d7; +$light-primary-text: $light-text; +$light-accent-text: rgba($light-primary-text, +0.7); +$light-disabled-text: rgba($light-primary-text, +0.5); +$light-dividers: rgba($light-primary-text, +0.12); +$light-focused: rgba($light-primary-text, +0.12); +$mat-dark-theme-foreground: ( base : $light-text, +divider : $light-dividers, +dividers : $light-dividers, +disabled : $light-disabled-text, +disabled-button : rgba($light-text, 0.3), +disabled-text : $light-disabled-text, +elevation : black, +hint-text : $light-disabled-text, +secondary-text : $light-accent-text, +accent-text : $light-accent-text, +icon : $light-text, +icons : $light-text, +text : $light-text, +slider-min : $light-text, +slider-off : rgba($light-text, 0.3), +slider-off-active: rgba($light-text, 0.3), +); +// Background config +// Light bg +$light-background : #dfd7d7; +$light-bg-darker-5 : darken($light-background, +5%); +$light-bg-darker-10 : darken($light-background, +10%); +$light-bg-darker-20 : darken($light-background, +20%); +$light-bg-darker-30 : darken($light-background, +30%); +$light-bg-lighter-5 : lighten($light-background, +5%); +$dark-bg-tooltip : lighten(#004165, +20%); +$dark-bg-alpha-4 : rgba(#004165, +0.04); +$dark-bg-alpha-12 : rgba(#004165, +0.12); +$mat-light-theme-background: ( background : $light-background, +status-bar : $light-bg-darker-20, +app-bar : $light-bg-darker-5, +hover : $dark-bg-alpha-4, +card : $light-bg-lighter-5, +dialog : $light-bg-lighter-5, +tooltip : $dark-bg-tooltip, +disabled-button : $dark-bg-alpha-12, +raised-button : $light-bg-lighter-5, +focused-button : $dark-focused, +selected-button : $light-bg-darker-20, +selected-disabled-button: $light-bg-darker-30, +disabled-button-toggle : $light-bg-darker-10, +unselected-chip : $light-bg-darker-10, +disabled-list-option : $light-bg-darker-10, +); +// Dark bg +$dark-background : #004165; +$dark-bg-lighter-5 : lighten($dark-background, +5%); +$dark-bg-lighter-10 : lighten($dark-background, +10%); +$dark-bg-lighter-20 : lighten($dark-background, +20%); +$dark-bg-lighter-30 : lighten($dark-background, +30%); +$light-bg-alpha-4 : rgba(#dfd7d7, +0.04); +$light-bg-alpha-12 : rgba(#dfd7d7, +0.12); +// Background palette for dark themes. +$mat-dark-theme-background: ( background : $dark-background, +status-bar : $dark-bg-lighter-20, +app-bar : $dark-bg-lighter-5, +hover : $light-bg-alpha-4, +card : $dark-bg-lighter-5, +dialog : $dark-bg-lighter-5, +tooltip : $dark-bg-lighter-20, +disabled-button : $light-bg-alpha-12, +raised-button : $dark-bg-lighter-5, +focused-button : $light-focused, +selected-button : $dark-bg-lighter-20, +selected-disabled-button: $dark-bg-lighter-30, +disabled-button-toggle : $dark-bg-lighter-10, +unselected-chip : $dark-bg-lighter-20, +disabled-list-option : $dark-bg-lighter-10, +); +// Compute font config +@include mat.core($fontConfig); +// Theme Config +body { + --primary-color: #0063ab; + --primary-lighter-color: #b3d0e6; + --primary-darker-color: #004791; + --text-primary-color: #{$light-primary-text}; + --text-primary-lighter-color: #{$dark-primary-text}; + --text-primary-darker-color: #{$light-primary-text}; +} + +$mat-primary: ( main: #0063ab, +lighter: #b3d0e6, +darker: #004791, +200: #0063ab, // For slide toggle, +contrast: ( main: $light-primary-text, lighter: $dark-primary-text, darker: $light-primary-text, )); +$theme-primary: mat.define-palette($mat-primary, +main, +lighter, +darker); +body { + --accent-color: #00a8e8; + --accent-lighter-color: #b3e5f8; + --accent-darker-color: #008dde; + --text-accent-color: #{$light-primary-text}; + --text-accent-lighter-color: #{$dark-primary-text}; + --text-accent-darker-color: #{$light-primary-text}; +} -// Define the palettes for your theme using the Material Design palettes available in palette.scss -// (imported above). For each palette, you can optionally specify a default, lighter, and darker -// hue. Available color palettes: https://material.io/design/color/ -$frontend-primary: mat.define-palette(mat.$indigo-palette); -$frontend-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400); +$mat-accent: ( main: #00a8e8, +lighter: #b3e5f8, +darker: #008dde, +200: #00a8e8, // For slide toggle, +contrast: ( main: $light-primary-text, lighter: $dark-primary-text, darker: $light-primary-text, )); +$theme-accent: mat.define-palette($mat-accent, +main, +lighter, +darker); +body { + --warn-color: #f9b7b7; + --warn-lighter-color: #fde9e9; + --warn-darker-color: #f69f9f; + --text-warn-color: #{$dark-primary-text}; + --text-warn-lighter-color: #{$dark-primary-text}; + --text-warn-darker-color: #{$dark-primary-text}; +} -// The warn palette is optional (defaults to red). -$frontend-warn: mat.define-palette(mat.$red-palette); +$mat-warn: ( main: #f9b7b7, +lighter: #fde9e9, +darker: #f69f9f, +200: #f9b7b7, // For slide toggle, +contrast: ( main: $dark-primary-text, lighter: $dark-primary-text, darker: $dark-primary-text, )); +$theme-warn: mat.define-palette($mat-warn, +main, +lighter, +darker); +; +$theme: ( primary: $theme-primary, +accent: $theme-accent, +warn: $theme-warn, +is-dark: true, +foreground: $mat-dark-theme-foreground, +background: $mat-dark-theme-background, +); +$altTheme: ( primary: $theme-primary, +accent: $theme-accent, +warn: $theme-warn, +is-dark: false, +foreground: $mat-light-theme-foreground, +background: $mat-light-theme-background, +); +// Theme Init +@include mat.all-component-themes($theme); +.theme-alternate { + @include mat.all-component-themes($altTheme); +} -// Create the theme object. A theme consists of configurations for individual -// theming systems such as "color" or "typography". -$frontend-theme: mat.define-light-theme(( - color: ( - primary: $frontend-primary, - accent: $frontend-accent, - warn: $frontend-warn, - ) -)); +// Specific component overrides, pieces that are not in line with the general theming +// Handle buttons appropriately, with respect to line-height +.mat-raised-button, +.mat-stroked-button, +.mat-flat-button { + padding: 0 1.15em; + margin: 0 .65em; + min-width: 3em; + line-height: 36.4px +} -// Include theme styles for core and each component used in your app. -// Alternatively, you can import and @include the theme mixins for each component -// that you are using. -@include mat.all-component-themes($frontend-theme); +.mat-standard-chip { + padding: .5em .85em; + min-height: 2.5em; +} +.material-icons { + font-size: 24px; + font-family: 'Material Icons', 'Material Icons'; + .mat-badge-content { + font-family: 'Roboto'; + } +}
\ No newline at end of file diff --git a/frontend/src/styles/helper.css b/frontend/src/styles/helper.css index 9c520ac3..875b94f1 100644 --- a/frontend/src/styles/helper.css +++ b/frontend/src/styles/helper.css @@ -51,4 +51,27 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} + +.btn-clear { + border: unset; + background-color: unset; + outline: unset; + position: relative; +} + +.input-icon { + color: var(--offwhite); + transform: translateY(25%); +} + +.input-icon:hover { + color: var(--ns-primary); +} + +.f-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; }
\ No newline at end of file diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index e24da375..33f4cf2b 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -1,5 +1,10 @@ /*Mora da se ispravi za media kada je ekran premali pa se poredjaju u kolonu*/ +html, +body { + height: 100%; +} + .align-items-view>*:first-child { transform: perspective(100em) rotateY(25deg) translateZ(1em); } diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css index d0ccc935..f2e84e8b 100644 --- a/frontend/src/styles/theme.css +++ b/frontend/src/styles/theme.css @@ -35,4 +35,22 @@ a { .text-offwhite { color: var(--offwhite) !important; +} + + +/* Ripple effect */ + +.bubble { + background-position: center; + transition: background 0.8s; +} + +.bubble:hover { + background: #47a7f5 radial-gradient(circle, transparent 1%, #47a7f5 1%) center/15000%; +} + +.bubble:active { + background-color: #6eb9f7; + background-size: 100%; + transition: background 0s; }
\ No newline at end of file |