diff options
Diffstat (limited to 'frontend/src/app')
189 files changed, 4671 insertions, 2149 deletions
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/Dataset.ts b/frontend/src/app/_data/Dataset.ts index 766040a3..4ff0a471 100644 --- a/frontend/src/app/_data/Dataset.ts +++ b/frontend/src/app/_data/Dataset.ts @@ -1,25 +1,27 @@ -export default class Dataset { +import { FolderFile } from "./FolderFile"; + +export default class Dataset extends FolderFile { _id: string = ''; constructor( - public name: string = 'Novi izvor podataka', + name: string = 'Novi izvor podataka', public description: string = '', - public header: string[] = [], public fileId?: number, public extension: string = '.csv', public isPublic: boolean = false, public accessibleByLink: boolean = false, - public dateCreated: Date = new Date(), - public lastUpdated: Date = new Date(), + dateCreated: Date = new Date(), + lastUpdated: Date = new Date(), public uploaderId: string = '', - public delimiter: string = '', - public hasHeader: boolean = true, + public delimiter: string = ',', public columnInfo: ColumnInfo[] = [], public rowCount: number = 0, public nullRows: number = 0, public nullCols: number = 0, - public preview: string[][] = [[]] - ) { } + public cMatrix: number[][] = [] + ) { + super(name, dateCreated, lastUpdated); + } } export class ColumnInfo { @@ -28,9 +30,20 @@ export class ColumnInfo { public isNumber: boolean = false, public numNulls: number = 0, public uniqueValues?: string[], + public uniqueValuesCount?: number[], + public uniqueValuesPercent?: number[], public median?: number, public mean?: number, public min?: number, - public max?: number - ) { } -}
\ No newline at end of file + public max?: number, + public q1?: number, + public q3?: number, + ) { + /*if (isNumber) + this.columnType = ColumnType.numerical; + else + this.columnType = ColumnType.categorical;*/ + } + +} + diff --git a/frontend/src/app/_data/Experiment.ts b/frontend/src/app/_data/Experiment.ts index 95ef6e1e..cff77535 100644 --- a/frontend/src/app/_data/Experiment.ts +++ b/frontend/src/app/_data/Experiment.ts @@ -5,6 +5,7 @@ export default class Experiment { constructor( public name: string = 'Novi eksperiment', public description: string = '', + public type: ProblemType = ProblemType.Regression, public datasetId: string = '', public inputColumns: string[] = [], public outputColumn: string = '', @@ -13,15 +14,11 @@ export default class Experiment { public dateCreated: Date = new Date(), public lastUpdated: Date = new Date(), public modelIds: string[] = [], - - // Test set settings - public randomOrder: boolean = true, - public randomTestSet: boolean = true, - public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U - - public encodings: ColumnEncoding[] = [], - public type: ProblemType = ProblemType.Regression + public columnTypes: ColumnType[] = [], + public encodings: ColumnEncoding[] = []//[{columnName: "", columnEncoding: Encoding.Label}] ) { } + + _columnsSelected: boolean = false; } export enum NullValueOptions { @@ -47,11 +44,11 @@ export class NullValReplacer { export enum Encoding { Label = 'label', OneHot = 'onehot', - Ordinal = 'ordinal', + /*Ordinal = 'ordinal', Hashing = 'hashing', Binary = 'binary', BaseN = 'baseN' - /* + BackwardDifference = 'backward difference', CatBoost = 'cat boost', Count = 'count', @@ -69,9 +66,13 @@ export enum Encoding { } export class ColumnEncoding { - constructor ( + constructor( public columnName: string, public encoding: Encoding - ) - {} + ) { } +} + +export enum ColumnType { + categorical = "categorical", + numerical = "numerical" }
\ No newline at end of file diff --git a/frontend/src/app/_data/FolderFile.ts b/frontend/src/app/_data/FolderFile.ts new file mode 100644 index 00000000..c228f25e --- /dev/null +++ b/frontend/src/app/_data/FolderFile.ts @@ -0,0 +1,14 @@ +export class FolderFile { + constructor( + public name: string, + public dateCreated: Date, + public lastUpdated: Date + ) { } +} + + +export enum FolderType { + Dataset, + Model, + Experiment +}
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index 7d383584..526a8290 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -1,12 +1,13 @@ import { NgIf } from "@angular/common"; +import { FolderFile } from "./FolderFile"; -export default class Model { +export default class Model extends FolderFile { _id: string = ''; constructor( - public name: string = 'Novi model', + name: string = 'Novi model', public description: string = '', - public dateCreated: Date = new Date(), - public lastUpdated: Date = new Date(), + dateCreated: Date = new Date(), + lastUpdated: Date = new Date(), //public experimentId: string = '', // Neural net training settings @@ -14,17 +15,65 @@ export default class Model { public optimizer: Optimizer = Optimizer.Adam, public lossFunction: LossFunction = LossFunction.MeanSquaredError, public inputNeurons: number = 1, - public hiddenLayerNeurons: number = 1, public hiddenLayers: number = 1, - public batchSize: number = 4, - public hiddenLayerActivationFunctions: string[] = ['sigmoid'], + public batchSize: BatchSize = BatchSize.O3, 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: LearningRate = LearningRate.LR1, + public layers: Layer[] = [new Layer()], + + // Test set settings + public randomOrder: boolean = true, + public randomTestSet: boolean = true, + public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U + + public isPublic: boolean = false, + public accessibleByLink: boolean = false + ) { + super(name, dateCreated, lastUpdated); + } +} +export class Layer { + constructor( + public layerNumber: number = 0, + public activationFunction: ActivationFunction = ActivationFunction.Sigmoid, + public neurons: number = 3, + public regularisation: Regularisation = Regularisation.L1, + public regularisationRate: RegularisationRate = RegularisationRate.RR1, ) { } } - +export enum LearningRate { + LR1 = '0.00001', + LR2 = '0.0001', + LR3 = '0.001', + LR4 = '0.003', + LR5 = '0.01', + LR6 = '0.03', + LR7 = '0.1', + LR8 = '0.3', + LR9 = '1', + LR10 = '3', + LR11 = '10', +} +export enum Regularisation { + L1 = 'l1', + L2 = 'l2' +} +export enum RegularisationRate { + RR1 = '0', + RR2 = '0.001', + RR3 = '0.003', + RR4 = '0.01', + RR5 = '0.03', + RR6 = '0.1', + RR7 = '0.3', + RR8 = '1', + RR9 = '3', + RR10 = '10', +} export enum ProblemType { Regression = 'regresioni', BinaryClassification = 'binarni-klasifikacioni', @@ -94,7 +143,7 @@ export enum LossFunctionBinaryClassification { HingeLoss = 'hinge_loss', } export enum LossFunctionMultiClassification { - //CategoricalCrossEntropy = 'categorical_crossentropy', + CategoricalCrossEntropy = 'categorical_crossentropy', SparseCategoricalCrossEntropy = 'sparse_categorical_crossentropy', KLDivergence = 'kullback_leibler_divergence', } @@ -157,3 +206,16 @@ export enum MetricsMultiClassification { Recall = 'recall_score', F1 = 'f1_score', } + +export enum BatchSize { + O1 = '2', + O2 = '4', + O3 = '8', + O4 = '16', + O5 = '32', + O6 = '64', + O7 = '128', + O8 = '256', + O9 = '512', + O10 = '1024' +}
\ No newline at end of file diff --git a/frontend/src/app/barchart/barchart.component.css b/frontend/src/app/_elements/_charts/barchart/barchart.component.css index c3634c9f..c3634c9f 100644 --- a/frontend/src/app/barchart/barchart.component.css +++ b/frontend/src/app/_elements/_charts/barchart/barchart.component.css diff --git a/frontend/src/app/barchart/barchart.component.html b/frontend/src/app/_elements/_charts/barchart/barchart.component.html index 48b7bd3e..48b7bd3e 100644 --- a/frontend/src/app/barchart/barchart.component.html +++ b/frontend/src/app/_elements/_charts/barchart/barchart.component.html diff --git a/frontend/src/app/barchart/barchart.component.spec.ts b/frontend/src/app/_elements/_charts/barchart/barchart.component.spec.ts index 8b346d1c..8b346d1c 100644 --- a/frontend/src/app/barchart/barchart.component.spec.ts +++ b/frontend/src/app/_elements/_charts/barchart/barchart.component.spec.ts diff --git a/frontend/src/app/barchart/barchart.component.ts b/frontend/src/app/_elements/_charts/barchart/barchart.component.ts index def64b7d..904335d7 100644 --- a/frontend/src/app/barchart/barchart.component.ts +++ b/frontend/src/app/_elements/_charts/barchart/barchart.component.ts @@ -8,6 +8,7 @@ import {Chart} from 'node_modules/chart.js'; }) export class BarchartComponent implements OnInit { + constructor() { } ngOnInit(){ diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.css b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.css +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.css 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 new file mode 100644 index 00000000..688eafae --- /dev/null +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html @@ -0,0 +1,3 @@ +<div class="chart-wrapper"> + <canvas #boxplot [width]="width" [height]="height"></canvas> +</div>
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.spec.ts b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.spec.ts index 1222cb40..759e7c5e 100644 --- a/frontend/src/app/training/training.component.spec.ts +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { TrainingComponent } from './training.component'; +import { BoxPlotComponent } from './box-plot.component'; -describe('TrainingComponent', () => { - let component: TrainingComponent; - let fixture: ComponentFixture<TrainingComponent>; +describe('BoxPlotComponent', () => { + let component: BoxPlotComponent; + let fixture: ComponentFixture<BoxPlotComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ TrainingComponent ] + declarations: [ BoxPlotComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(TrainingComponent); + fixture = TestBed.createComponent(BoxPlotComponent); component = fixture.componentInstance; fixture.detectChanges(); }); 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 new file mode 100644 index 00000000..d6f4b6ec --- /dev/null +++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts @@ -0,0 +1,102 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Chart, LinearScale, CategoryScale } from 'chart.js'; +import { BoxPlotController, BoxAndWiskers } from '@sgratzl/chartjs-chart-boxplot'; + +function randomValues(count: number, min: number, max: number) { + const delta = max - min; + return Array.from({ length: count }).map(() => Math.random() * delta + min); +} + +Chart.register(BoxPlotController, BoxAndWiskers, LinearScale, CategoryScale); + +@Component({ + selector: 'app-box-plot', + templateUrl: './box-plot.component.html', + styleUrls: ['./box-plot.component.css'] +}) +export class BoxPlotComponent implements AfterViewInit { + + @Input()width?: number; + @Input()height?: number; + + @ViewChild('boxplot') chartRef!: ElementRef; + constructor() { } + + boxplotData = { + // define label tree + labels: ['January'/*, 'February', 'March', 'April', 'May', 'June', 'July'*/], + datasets: [{ + label: 'Dataset 1', + backgroundColor: '#0063AB', + borderColor: '#dfd7d7', + borderWidth: 1, + outlierColor: '#999999', + scaleFontColor: '#0063AB', + padding: 10, + itemRadius: 0, + data: [ + randomValues(100, 0, 100), + /*randomValues(100, 0, 20), + randomValues(100, 20, 70), + randomValues(100, 60, 100), + randomValues(40, 50, 100), + randomValues(100, 60, 120), + randomValues(100, 80, 100)*/ + ]}/*, { + label: 'Dataset 2', + backgroundColor: 'rgba(0,0,255,0.5)', + borderColor: 'blue', + borderWidth: 1, + outlierColor: '#999999', + padding: 10, + itemRadius: 0, + data: [ + randomValues(100, 60, 100), + randomValues(100, 0, 100), + randomValues(100, 0, 20), + randomValues(100, 20, 70), + randomValues(40, 60, 120), + randomValues(100, 20, 100), + randomValues(100, 80, 100) + ] + }*/] + }; + ngAfterViewInit(): void { + const myChart = new Chart(this.chartRef.nativeElement, { + type: "boxplot", + data: this.boxplotData, + options: { + /*title: { + display: true, + text: 'Predicted world population (millions) in 2050' + }*/ + plugins:{ + legend: { + display: false + }, + }, + scales : { + x: { + ticks: { + color: '#dfd7d7' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + }, + y : { + min: -50, + max: 200, + ticks: { + color: '#dfd7d7' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + } + } + } + }); +} + +} diff --git a/frontend/src/app/_elements/carousel/carousel.component.css b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_elements/carousel/carousel.component.css +++ 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..9c464534 --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.html @@ -0,0 +1 @@ +<canvas #doughnut [width]="width" [height]="height"></canvas> diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.spec.ts b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.spec.ts index b5c2d91c..67309670 100644 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.spec.ts +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ItemPredictorComponent } from './item-predictor.component'; +import { DoughnutChartComponent } from './doughnut-chart.component'; -describe('ItemPredictorComponent', () => { - let component: ItemPredictorComponent; - let fixture: ComponentFixture<ItemPredictorComponent>; +describe('DoughnutChartComponent', () => { + let component: DoughnutChartComponent; + let fixture: ComponentFixture<DoughnutChartComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ItemPredictorComponent ] + declarations: [ DoughnutChartComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ItemPredictorComponent); + fixture = TestBed.createComponent(DoughnutChartComponent); component = fixture.componentInstance; fixture.detectChanges(); }); 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..fc13289c --- /dev/null +++ b/frontend/src/app/_elements/_charts/doughnut-chart/doughnut-chart.component.ts @@ -0,0 +1,37 @@ +import { AfterViewInit, Component, ElementRef, Input, 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 { + + @Input()width: number = 800; + @Input()height: number = 450; + + @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/item-experiment/item-experiment.component.css b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_elements/item-experiment/item-experiment.component.css +++ b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.css diff --git a/frontend/src/app/_elements/_charts/heatmap/heatmap.component.html b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.html new file mode 100644 index 00000000..52d95516 --- /dev/null +++ b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.html @@ -0,0 +1,3 @@ +<div style="width:800px; height: 400px; background-color: red;"> + <ejs-heatmap [dataSource]='dataSource' [xAxis]='xAxis' [yAxis]='yAxis'></ejs-heatmap> +</div> diff --git a/frontend/src/app/_pages/predict/predict.component.spec.ts b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.spec.ts index 65871ecc..fa0a90cc 100644 --- a/frontend/src/app/_pages/predict/predict.component.spec.ts +++ b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { PredictComponent } from './predict.component'; +import { HeatmapComponent } from './heatmap.component'; -describe('PredictComponent', () => { - let component: PredictComponent; - let fixture: ComponentFixture<PredictComponent>; +describe('HeatmapComponent', () => { + let component: HeatmapComponent; + let fixture: ComponentFixture<HeatmapComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ PredictComponent ] + declarations: [ HeatmapComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(PredictComponent); + fixture = TestBed.createComponent(HeatmapComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/_charts/heatmap/heatmap.component.ts b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.ts new file mode 100644 index 00000000..f3d1af98 --- /dev/null +++ b/frontend/src/app/_elements/_charts/heatmap/heatmap.component.ts @@ -0,0 +1,108 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'chart.js'; +import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; + + + +@Component({ + selector: 'app-heatmap', + templateUrl: './heatmap.component.html', + styleUrls: ['./heatmap.component.css'] +}) +export class HeatmapComponent implements OnInit { + + + @Input()width?: number; + @Input()height?: number; + + dataSource = [ + + [73, 39, 26, 39, 94, 0], + + [93, 58, 53, 38, 26, 68], + + [99, 28, 22, 4, 66, 90], + + [14, 26, 97, 69, 69, 3], + + [7, 46, 47, 47, 88, 6], + + [41, 55, 73, 23, 3, 79], + + [56, 69, 21, 86, 3, 33], + + [45, 7, 53, 81, 95, 79], + + [60, 77, 74, 68, 88, 51], + + [25, 25, 10, 12, 78, 14], + + [25, 56, 55, 58, 12, 82], + + [74, 33, 88, 23, 86, 59] + + ]; + + xAxis = { + + labels: ['Nancy', 'Andrew', 'Janet', 'Margaret', 'Steven', 'Michael', 'Robert', + + 'Laura', 'Anne', 'Paul', 'Karin', 'Mario'], + + }; + + yAxis = { + + labels: ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'], + + } + + //@ViewChild('heatmap') chartRef!: ElementRef; + constructor() { } + + ngOnInit(): void { + + + + /* + const myChart = new Chart(this.chartRef.nativeElement, { + type: 'pie', + data: { + datasets: [{ + data: [ + + [73, 39, 26, 39, 94, 0], + + [93, 58, 53, 38, 26, 68], + + [99, 28, 22, 4, 66, 90], + + [14, 26, 97, 69, 69, 3], + + [7, 46, 47, 47, 88, 6], + + [41, 55, 73, 23, 3, 79], + + [56, 69, 21, 86, 3, 33], + + [45, 7, 53, 81, 95, 79], + + [60, 77, 74, 68, 88, 51], + + [25, 25, 10, 12, 78, 14], + + [25, 56, 55, 58, 12, 82], + + [74, 33, 88, 23, 86, 59] + + ], + }] + } +}); + */ + + } + +} diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.css b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_elements/line-chart/line-chart.component.css +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html new file mode 100644 index 00000000..7f18256a --- /dev/null +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html @@ -0,0 +1,3 @@ + + <canvas id="myChart" style="width: 100%; height: 530px;"> + </canvas>
\ No newline at end of file diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.spec.ts b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.spec.ts index 0c5e7ef5..0c5e7ef5 100644 --- a/frontend/src/app/_elements/line-chart/line-chart.component.spec.ts +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.spec.ts diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.ts b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts index 1a8579a0..655db9ec 100644 --- a/frontend/src/app/_elements/line-chart/line-chart.component.ts +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, AfterViewInit, ViewChild } from '@angular/core'; import { Chart } from 'chart.js'; @Component({ @@ -7,7 +7,7 @@ import { Chart } from 'chart.js'; styleUrls: ['./line-chart.component.css'] }) -export class LineChartComponent implements OnInit { +export class LineChartComponent implements AfterViewInit { dataAcc: number[] = []; dataMAE: number[] = []; @@ -47,7 +47,7 @@ export class LineChartComponent implements OnInit { this.myChart.update(); } - ngOnInit(): void { + ngAfterViewInit(): void { this.myChart = new Chart("myChart", { type: 'line', @@ -77,10 +77,27 @@ export class LineChartComponent implements OnInit { }, options: { scales: { + x: { + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + }, y: { - beginAtZero: true + beginAtZero: true, + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } } + } + + } } ); diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.css b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/mixed-chart/mixed-chart.component.css +++ b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.css diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.html b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.html index 806ea9e8..806ea9e8 100644 --- a/frontend/src/app/mixed-chart/mixed-chart.component.html +++ b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.html diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.spec.ts b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.spec.ts index 361cd047..361cd047 100644 --- a/frontend/src/app/mixed-chart/mixed-chart.component.spec.ts +++ b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.spec.ts diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.ts b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.ts index 2524ee36..2524ee36 100644 --- a/frontend/src/app/mixed-chart/mixed-chart.component.ts +++ b/frontend/src/app/_elements/_charts/mixed-chart/mixed-chart.component.ts diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.css b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.css +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.css 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 new file mode 100644 index 00000000..7faf3af0 --- /dev/null +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html @@ -0,0 +1,3 @@ +<div class="chart-wrapper"> + <canvas #piechart [width]="width" [height]="height"></canvas> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/my-models/my-models.component.spec.ts b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.spec.ts index e431d04c..64f36b7d 100644 --- a/frontend/src/app/_pages/my-models/my-models.component.spec.ts +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MyModelsComponent } from './my-models.component'; +import { PieChartComponent } from './pie-chart.component'; -describe('MyModelsComponent', () => { - let component: MyModelsComponent; - let fixture: ComponentFixture<MyModelsComponent>; +describe('PieChartComponent', () => { + let component: PieChartComponent; + let fixture: ComponentFixture<PieChartComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ MyModelsComponent ] + declarations: [ PieChartComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(MyModelsComponent); + fixture = TestBed.createComponent(PieChartComponent); component = fixture.componentInstance; fixture.detectChanges(); }); 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 new file mode 100644 index 00000000..f141f522 --- /dev/null +++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts @@ -0,0 +1,46 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'chart.js'; + +@Component({ + selector: 'app-pie-chart', + templateUrl: './pie-chart.component.html', + styleUrls: ['./pie-chart.component.css'] +}) +export class PieChartComponent implements AfterViewInit { + + @Input()width?: number; + @Input()height?: number; + + @ViewChild('piechart') chartRef!: ElementRef; + constructor() { } + + 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' + }*/ + plugins:{ + legend: { + display: false + }, + }, + layout: { + padding: 15} + } +}); + + } + + +} diff --git a/frontend/src/app/point-linechart/point-linechart.component.css b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/point-linechart/point-linechart.component.css +++ b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.css diff --git a/frontend/src/app/point-linechart/point-linechart.component.html b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.html index f9f9a24a..f9f9a24a 100644 --- a/frontend/src/app/point-linechart/point-linechart.component.html +++ b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.html diff --git a/frontend/src/app/point-linechart/point-linechart.component.spec.ts b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.spec.ts index fe08fe7c..fe08fe7c 100644 --- a/frontend/src/app/point-linechart/point-linechart.component.spec.ts +++ b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.spec.ts diff --git a/frontend/src/app/point-linechart/point-linechart.component.ts b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.ts index 3497a20c..3497a20c 100644 --- a/frontend/src/app/point-linechart/point-linechart.component.ts +++ b/frontend/src/app/_elements/_charts/point-linechart/point-linechart.component.ts diff --git a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css new file mode 100644 index 00000000..005cb692 --- /dev/null +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css @@ -0,0 +1,4 @@ +#divScatterChart{ + + display: block; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html new file mode 100644 index 00000000..ef41775a --- /dev/null +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html @@ -0,0 +1,4 @@ + +<div id="divScatterChart" style="width: 100%;height: 100%;"> + <canvas id="ScatterCharts" style="width: 100%;height: 280px;"> </canvas> +</div>
\ No newline at end of file diff --git a/frontend/src/app/scatterchart/scatterchart.component.spec.ts b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.spec.ts index 1db81051..1db81051 100644 --- a/frontend/src/app/scatterchart/scatterchart.component.spec.ts +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.spec.ts diff --git a/frontend/src/app/scatterchart/scatterchart.component.ts b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts index 9dfef4c3..12795c70 100644 --- a/frontend/src/app/scatterchart/scatterchart.component.ts +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts @@ -24,15 +24,32 @@ export class ScatterchartComponent implements OnInit { {x: 15, y: 5}, {x: 16, y: 3}, {x: 17, y: 2}], - backgroundColor: 'rgb(255, 99, 132)' + borderColor: 'white', }] }, options: { scales: { + x:{ + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + }, y: { - beginAtZero: true + beginAtZero: true, + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } } + } + + } }); } diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html deleted file mode 100644 index e5d4cd23..00000000 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html +++ /dev/null @@ -1,55 +0,0 @@ -<div class="row mb-4"> - <div class="col-2"> - </div> - <div class="col-3"> - <label for="name" class="col-form-label">Naziv dataseta:</label> - <input type="text" class="form-control mb-1" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name"> - - <label for="desc" class="col-sm-2 col-form-label">Opis:</label> - <div> - <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> - </div> - - <label for="checkboxIsPublic" class="form-check-label mt-3 mb-1">Želite li da dataset bude javan? - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.isPublic" (change)="checkAccessible()" type="checkbox" - value="" id="checkboxIsPublic"> - </label> - - <label for="checkboxAccessibleByLink" class="form-check-label">Želite li da bude deljiv linkom? - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.accessibleByLink" type="checkbox" - value="" id="checkboxAccessibleByLink"> - </label> - </div> - <div class="col-1"> - </div> - <div class="col-3 mt-4"> - - <label for="type" class="col-form-label">Izaberite delimiter za .csv fajl</label> - <select id="delimiterOptions" class="form-select" name="type" [(ngModel)]="dataset.delimiter" (change)="update()"> - <option - *ngFor="let option of delimiterOptions"> - {{ option }} - </option> - </select> - <!-- - <input list="delimiterOptions" placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" [(ngModel)]="dataset.delimiter" (input)="update()"> - <datalist id="delimiterOptions"> - <option *ngFor="let option of delimiterOptions">{{option}}</option> - </datalist> ---> - <label for="type" class="form-check-label my-5">Da li .csv ima header? - <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="dataset.hasHeader" type="checkbox" - value="" id="checkboxHeader" checked> - </label> - <br> - <input id="fileInput" class="form-control btn-lg" type="file" class="upload" (change)="changeListener($event)" accept=".csv"> - </div> -</div> - -<div class="px-5 mt-5"> - <app-datatable [tableData]="tableData"></app-datatable> -</div> - -<div class="d-flex flex-row align-items-center justify-content-center w-100 my-2"> - <button (click)="uploadDataset()" class="btn btn-lg col-4" style="background-color:#003459; color:white;">Dodaj izvor podataka</button> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/annvisual/annvisual.component.css b/frontend/src/app/_elements/annvisual/annvisual.component.css deleted file mode 100644 index 857a3390..00000000 --- a/frontend/src/app/_elements/annvisual/annvisual.component.css +++ /dev/null @@ -1,4 +0,0 @@ -#graph{ - width: 100%; - text-align: center; -}
\ No newline at end of file diff --git a/frontend/src/app/_elements/annvisual/annvisual.component.html b/frontend/src/app/_elements/annvisual/annvisual.component.html deleted file mode 100644 index 09251398..00000000 --- a/frontend/src/app/_elements/annvisual/annvisual.component.html +++ /dev/null @@ -1,5 +0,0 @@ -<div style="text-align: center; " > - <button (click)="d3()" mat-raised-button color="primary">Prikaz veštačke neuronske mreže</button> - <div id="graph" align-items-center style="width: 12rem;"></div> - </div> - diff --git a/frontend/src/app/_elements/annvisual/annvisual.component.ts b/frontend/src/app/_elements/annvisual/annvisual.component.ts deleted file mode 100644 index df0a3898..00000000 --- a/frontend/src/app/_elements/annvisual/annvisual.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import Model from 'src/app/_data/Model'; -import { graphviz } from 'd3-graphviz'; - -@Component({ - selector: 'app-annvisual', - templateUrl: './annvisual.component.html', - styleUrls: ['./annvisual.component.css'] -}) -export class AnnvisualComponent implements OnInit { - ngOnInit(): void { - } - - @Input() model: Model = new Model(); - - d3() { - let inputlayerstring: string = ''; - let hiddenlayerstring: string = ''; - let digraphstring: string = 'digraph {'; - - for (let i = 0; i < /*this.model.inputColumns.length*/ 10; i++) { - inputlayerstring = inputlayerstring + 'i' + i + ','; - } - inputlayerstring = inputlayerstring.slice(0, -1); - - digraphstring = digraphstring + inputlayerstring + '->'; - - for (let j = 0; j < this.model.hiddenLayers; j++) { - for (let i = 0; i < this.model.hiddenLayerNeurons; i++) { - hiddenlayerstring = hiddenlayerstring + 'h' + j + '_' + i + ','; - } - hiddenlayerstring = hiddenlayerstring.slice(0, -1); - digraphstring = digraphstring + hiddenlayerstring + '->'; - hiddenlayerstring = ''; - } - digraphstring = digraphstring + 'o}'; - - graphviz('#graph').renderDot(digraphstring); - } - - //'digraph {i0,i1,i2->h1,h2,h3->h21,h22,h23->o}' -} - - - diff --git a/frontend/src/app/_elements/carousel/carousel.component.html b/frontend/src/app/_elements/carousel/carousel.component.html deleted file mode 100644 index eb1041ce..00000000 --- a/frontend/src/app/_elements/carousel/carousel.component.html +++ /dev/null @@ -1,17 +0,0 @@ -<div class="container"> - <div class="row d-flex align-items-stretch flex-row mx-5 align-items-stretch"> - <div class="col my-1" *ngFor="let item of items" [ngSwitch]="type"> - <ng-template ngSwitchCase="Object"> - Unknown item type - </ng-template> - <ng-template ngSwitchCase="Dataset"> - <app-item-dataset [dataset]="item"> - </app-item-dataset> - </ng-template> - <ng-template ngSwitchCase="Predictor"> - <app-item-predictor [predictor]="item"> - </app-item-predictor> - </ng-template> - </div> - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/carousel/carousel.component.ts b/frontend/src/app/_elements/carousel/carousel.component.ts deleted file mode 100644 index e0112121..00000000 --- a/frontend/src/app/_elements/carousel/carousel.component.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-carousel', - templateUrl: './carousel.component.html', - styleUrls: ['./carousel.component.css'] -}) -export class CarouselComponent { - - @Input() items: any[] = []; - @Input() type: string = "Object"; - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/frontend/src/app/_elements/column-table/column-table.component.css b/frontend/src/app/_elements/column-table/column-table.component.css new file mode 100644 index 00000000..0477b7be --- /dev/null +++ b/frontend/src/app/_elements/column-table/column-table.component.css @@ -0,0 +1,266 @@ +table.fixed { + table-layout: fixed; + display: block; + overflow-x: auto; + white-space: nowrap; + font-size: 12px; + border-radius: 4px; +} + +#divTable { + height: 100%; + overflow-y: auto; +} + +table.fixed td { + overflow: hidden; + max-width: 200px; + min-width: 200px; + vertical-align: middle; + margin: 4px; +} + +table.fixed th { + overflow: hidden; + max-width: 120px; + min-width: 120px; + vertical-align: middle; + background-color: var(--ns-primary-50); + font-size: 14px; +} + +table.fixed th:first-child { + text-align: center; +} + +.columnNames { + background-color: var(--ns-primary-50) !important; +} + +.brighter { + background-color: var(--ns-primary) !important; + border-color: var(--offwhite); +} + +.border-bottom { + border-bottom-color: var(--offwhite) !important; +} + +mat-slider { + width: 300px; +} + +.slider { + background-color: var(--ns-bg-dark-100); +} + +#missingValuesHeader { + font-size: 13px; + line-height: 140% !important; +} + +.verticalAlign { + vertical-align: center; +} + +.cell-align { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + vertical-align: middle; + height: 100%; +} + +.text-left { + text-align: left !important; +} + +table ::ng-deep .mat-form-field-wrapper { + margin-top: -2rem; +} + +.graphics-row { + height: 100px; + padding: 1px; + margin: 0; +} + +.no-pad { + padding: 2px; + margin: 0; +} + +.text-overflow { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.row-height { + height: 30px; + border: none; + outline: none; +} + +.graphic-class { + opacity: 0.5; +} + + +/* TABS STYLE */ + +#folder-table { + border: 1px solid var(--ns-primary); + border-radius: 4px; + height: 70%; +} + +#tabs { + display: flex; + flex-direction: row; + align-items: flex-end; + height: 3.2rem; +} + +#tabs>.folder-tab:not(:first-child) { + margin-left: -5px; +} + +.folder-tab-end { + margin-left: auto; + color: var(--offwhite) !important; + overflow: hidden; +} + +.folder-tab, +.folder-tab-end { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + position: relative; + overflow-x: hidden; + 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; +} + +.folder-tab:not(:first-child) { + margin-block-start: auto; +} + +.folder-tab { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.selected-tab { + height: 3rem; + background-color: var(--ns-primary); + color: var(--offwhite); +} + +.hover-tab { + height: 3.2rem; +} + +.selected-tab, +.hover-tab { + width: fit-content !important; +} + +.tab-link { + color: var(--offwhite) !important; + text-decoration: none !important; + cursor: pointer; +} + +.tab-link:active { + text-decoration: underline !important; +} + +.selected-tab { + background-color: var(--ns-primary); +} + +.hidden { + /*visibility: hidden; + height: 1px;*/ + display: none; +} + +.bottom-button { + font-size: large; + position: relative; + background-color: var(--ns-primary); + width: 10rem; + height: 2.3rem; + border-color: var(--ns-primary); + border-style: solid; + border-width: 0px 1px 1px 1px; +} + +#footer { + display: flex; + flex-direction: row; + justify-content: center; +} + +.pad-fix { + padding-top: 0.6rem; + padding-bottom: 0; +} + +.long { + height: 3rem; +} + +.col-disabled { + background-color: rgb(0, 45, 69); +} + +.text-disabled { + color: gray; +} + +.header-disabled { + color: gray; +} + +.menu-disabled { + pointer-events: none; + opacity: .5; +} + +col:not(.col-disabled) { + background-color: var(--ns-bg-dark-100); +} + +.col-first { + background-color: rgb(1, 56, 86) !important; +} + + +/* mat-icon rotate */ + +.rotate { + animation: rotation 3s infinite linear; +} + +.rotate:hover { + cursor: pointer; +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/column-table/column-table.component.html b/frontend/src/app/_elements/column-table/column-table.component.html new file mode 100644 index 00000000..efc093d2 --- /dev/null +++ b/frontend/src/app/_elements/column-table/column-table.component.html @@ -0,0 +1,249 @@ +<div *ngIf="loaded"> + <div id="tabs"> + <div class="folder-tab p-1 rounded-top" *ngFor="let tab of tabs; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedTab.index == i, 'hover-tab' : hoveringOverTab?.index == i}"> + <a class="m-1 stretched-link tab-link" (click)="selectTab(i)" (mouseenter)="hoverOverTab(i)" (mouseleave)="hoverOverTab(-1)"> + {{tab.name}} + </a> + </div> + <!-- + <button mat-button class="p-1 folder-tab-end rounded-top"> + Kolone + <mat-icon>keyboard_double_arrow_down</mat-icon> + </button> + --> + </div> + <div id="folder-table" *ngIf="dataset && experiment"> + <!--<div [ngSwitch]="tabToDisplay">--> + <div id="divTable"> + + <div [ngClass]="{'hidden': tabToDisplay != Table.Data}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>#</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of tableData; let i = index"> + <th>#{{i}}</th> + <td *ngFor="let col of row; let j = index" [ngClass]="{'text-disabled' : !columnsChecked[j]}"> + <div class="text-overflow"> + {{col}} + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div [ngClass]="{'hidden': tabToDisplay != Table.CorrelationMatrix}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>Kolona</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of dataset.cMatrix; let i = index"> + <th [ngClass]="{'header-disabled col-disabled' : !columnsChecked[i]}"> + <div class="text-left"> + {{dataset.columnInfo[i].columnName}} + </div> + </th> + <td *ngFor="let corrVal of row; let j = index" [ngClass]="{'text-disabled col-disabled' : !columnsChecked[j] || !columnsChecked[i]}"> + <div class="text-overflow"> + {{corrVal}} + </div> + </td> + </tr> + </tbody> + </table> + </div> + + <div [ngClass]="{'hidden': tabToDisplay != Table.Columns}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>Naziv</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr> + <th>Tip</th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + <p class="verticalAlign text-left" style="font-size:13px;" *ngIf="!colInfo.isNumber">Kategorijski</p> + <mat-form-field *ngIf="colInfo.isNumber"> + <mat-select matNativeControl [(value)]="this.experiment.columnTypes[i]" [disabled]="!columnsChecked[i]" (selectionChange)="columnTypeChanged(colInfo.columnName);"> + <mat-option [value]="ColumnType.categorical">Kategorijski</mat-option> + <mat-option [value]="ColumnType.numerical">Numerički</mat-option> + </mat-select> + </mat-form-field> + </td> + </tr> + <tr class="graphics-row"> + <th class="no-pad">Grafik</th> + <td class="no-pad" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'graphic-class' : !columnsChecked[i]}"> + <app-box-plot *ngIf="this.experiment.columnTypes[i] == ColumnType.numerical" [width]="150" [height]="150"></app-box-plot> + <app-pie-chart *ngIf="this.experiment.columnTypes[i] == ColumnType.categorical" [width]="150" [height]="150"></app-pie-chart> + </td> + </tr> + <tr> + <th class="border-bottom">Statistika</th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'text-disabled' : !columnsChecked[i]}" class="text-left"> + <span *ngIf="this.experiment.columnTypes[i] == ColumnType.numerical"> + Mean: {{colInfo.mean}}<br> + Median: {{colInfo.median}}<br> + Min: {{colInfo.min}}<br> + Max: {{colInfo.max}}<br> + Q1: {{colInfo.q1}}<br> + Q3: {{colInfo.q3}}<br> + </span> + <div class="text-overflow" *ngIf="this.experiment.columnTypes[i] == ColumnType.categorical && colInfo.uniqueValuesPercent"> + <span *ngFor="let uniqueValue of colInfo.uniqueValues | slice:0:6; let i = index"> + ({{(colInfo.uniqueValuesPercent[i] * 100).toFixed(2)}}%) {{uniqueValue}}<br> + </span> + </div> + </td> + </tr> + <tr style="padding: 0"> + <th class="brighter cell-align long" (click)="openEncodingDialog()"> + <span class="verticalAlign">Enkodiranje</span> + <span class="material-icons-round verticalAlign rotate">settings</span> + </th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + <mat-form-field> + <mat-select matNativeControl [(value)]="experiment.encodings[i].encoding" [disabled]="!columnsChecked[i]" (selectionChange)="columnTableChangeDetected()"> + <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </td> + </tr> + <tr> + <th class="brighter cell-align" (click)="openMissingValuesDialog()"> + <div id="missingValuesHeader">Nedostajuće<br>vrednosti<br></div> + <span class="material-icons-round rotate">settings</span> + </th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + + <div *ngIf="colInfo.numNulls > 0"> + <button class="w-100" mat-raised-button [ngClass]="{ 'menu-disabled' : !columnsChecked[i]}" [matMenuTriggerFor]="menu" id="main_{{colInfo.columnName}}" #nullValMenu> + <div class="cell-align"> + {{nullValOption[i]}} + <mat-icon>arrow_drop_down</mat-icon> + </div> + </button> + <mat-menu #menu="matMenu"> + <!--<button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteColumns, i)" value={{colInfo.columnName}}>Obriši kolonu</button>--> + <button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteRows, i)" value={{colInfo.columnName}}>Obriši redove ({{colInfo.numNulls}})</button> + <button mat-menu-item [matMenuTriggerFor]="fillWith">Popuni sa</button> + </mat-menu> + + <mat-menu #fillWith="matMenu"> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.min}}>Min ({{colInfo.min}})</button> + + <button *ngIf="!colInfo.isNumber" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button> + + <button mat-menu-item [matMenuTriggerFor]="replaceWith">Unesi vrednost...</button> + </mat-menu> + + <mat-menu #uniques="matMenu"> + <button mat-menu-item *ngFor="let uniqueValue of colInfo.uniqueValues" (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{uniqueValue}}>{{uniqueValue}}</button> + </mat-menu> + + <mat-menu #replaceWith="matMenu"> + <input type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue> + <button [disabled]="getValue(colInfo.columnName) == ''" mat-menu-item value={{getValue(colInfo.columnName)}} (click)="MissValsReplaceClicked($event, colInfo.columnName, i)">Potvrdi unos</button> + </mat-menu> + </div> + <div *ngIf="colInfo.numNulls == 0" class="text-left"> + Nema nedostajućih vrednosti. + </div> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + <div *ngIf="dataset && experiment" class="container-fluid text-offwhite belowColumn mt-3"> + <div class="ns-row"> + <div class="break-2"></div> + + <div class="ns-col rounded"> + <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> + <mat-label>Izlazna kolona</mat-label> + <mat-select [(value)]="experiment.outputColumn" (selectionChange)="changeOutputColumn(this.experiment.inputColumns[0])"> + <mat-option *ngFor="let inputColumn of experiment.inputColumns" [value]="inputColumn">{{inputColumn}}</mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="ns-col rounded"> + <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> + <mat-label>Tip problema</mat-label> + <mat-select [(value)]="experiment.type"> + <mat-option [value]="ProblemType.Regression">Regresioni</mat-option> + <mat-option [value]="ProblemType.BinaryClassification">Binarni-klasifikacioni</mat-option> + <mat-option [value]="ProblemType.MultiClassification">Multi-klasifikacioni</mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="break-1"></div> + <div class="ns-col d-flex align-items-center justify-content-center"> + <button *ngIf="experiment._id == ''" mat-button (click)="saveExperiment()" class="bottom-button text-offwhite rounded-bottom"> + <div class="f-row" style="justify-content: space-around; width: 100%;"> + <div>Sačuvaj</div> + <div class="icon-double pt-1"> + <mat-icon>check</mat-icon> + <mat-icon>check</mat-icon> + </div> + </div> + </button> + <button *ngIf="experiment._id != ''" mat-button (click)="updateExperiment()" class="bottom-button text-offwhite rounded-bottom"> + <div class="f-row" style="justify-content: space-around; width: 100%;"> + <div>Sačuvaj izmene</div> + <div class="icon-double pt-1"> + <mat-icon>check</mat-icon> + <mat-icon>check</mat-icon> + </div> + </div> + </button> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.spec.ts b/frontend/src/app/_elements/column-table/column-table.component.spec.ts index 603889b2..360a8109 100644 --- a/frontend/src/app/_elements/item-dataset/item-dataset.component.spec.ts +++ b/frontend/src/app/_elements/column-table/column-table.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ItemDatasetComponent } from './item-dataset.component'; +import { ColumnTableComponent } from './column-table.component'; -describe('ItemDatasetComponent', () => { - let component: ItemDatasetComponent; - let fixture: ComponentFixture<ItemDatasetComponent>; +describe('ColumnTableComponent', () => { + let component: ColumnTableComponent; + let fixture: ComponentFixture<ColumnTableComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ItemDatasetComponent ] + declarations: [ ColumnTableComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ItemDatasetComponent); + fixture = TestBed.createComponent(ColumnTableComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/column-table/column-table.component.ts b/frontend/src/app/_elements/column-table/column-table.component.ts new file mode 100644 index 00000000..c200e674 --- /dev/null +++ b/frontend/src/app/_elements/column-table/column-table.component.ts @@ -0,0 +1,346 @@ +import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChildren } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import Experiment, { ColumnEncoding, Encoding, ColumnType, NullValueOptions } from 'src/app/_data/Experiment'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import { EncodingDialogComponent } from 'src/app/_modals/encoding-dialog/encoding-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { MissingvaluesDialogComponent } from 'src/app/_modals/missingvalues-dialog/missingvalues-dialog.component'; +import { MatCheckboxChange } from '@angular/material/checkbox'; +import { CsvParseService } from 'src/app/_services/csv-parse.service'; +import { ProblemType } from 'src/app/_data/Model'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; +import { SaveExperimentDialogComponent } from 'src/app/_modals/save-experiment-dialog/save-experiment-dialog.component'; +import { AlertDialogComponent } from 'src/app/_modals/alert-dialog/alert-dialog.component'; +import Shared from 'src/app/Shared'; + +@Component({ + selector: 'app-column-table', + templateUrl: './column-table.component.html', + styleUrls: ['./column-table.component.css'] +}) +export class ColumnTableComponent implements AfterViewInit { + + @Input() dataset?: Dataset; + @Input() experiment!: Experiment; + @Output() okPressed: EventEmitter<string> = new EventEmitter(); + @Output() columnTableChanged = new EventEmitter(); + + Object = Object; + Encoding = Encoding; + NullValueOptions = NullValueOptions; + ColumnType = ColumnType; + ProblemType = ProblemType; + tableData?: any[][]; + nullValOption: string[] = []; + + columnsChecked: boolean[] = []; //niz svih kolona + loaded: boolean = false; + + + constructor(private datasetService: DatasetsService, private experimentService: ExperimentsService, public csvParseService: CsvParseService, public dialog: MatDialog) { + //ovo mi nece trebati jer primam dataset iz druge komponente + } + + loadDataset(dataset: Dataset) { + this.dataset = dataset; + + this.setColumnTypeInitial(); + + this.dataset.columnInfo.forEach(column => { + this.columnsChecked.push(true); + }); + + this.resetInputColumns(); + this.resetOutputColumn(); + this.resetColumnEncodings(Encoding.Label); + this.setDeleteRowsForMissingValTreatment(); + + this.nullValOption = []; + this.dataset.columnInfo.forEach(colInfo => { + this.nullValOption.push(`Obriši redove (${colInfo.numNulls})`); + }); + + this.datasetService.getDatasetFilePartial(this.dataset.fileId, 0, 10).subscribe((response: string | undefined) => { + if (response && this.dataset != undefined) { + this.tableData = this.csvParseService.csvToArray(response, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter.toString() == "") ? "," : this.dataset.delimiter); + } + }); + this.loaded = true; + } + + ngAfterViewInit(): void { + + } + + setColumnTypeInitial() { + if (this.dataset != undefined) { + for (let i = 0; i < this.dataset.columnInfo.length; i++) { + this.experiment.columnTypes[i] = (this.dataset.columnInfo[i].isNumber) ? ColumnType.numerical : ColumnType.categorical; + } + } + } + + resetInputColumns() { + if (this.dataset != undefined) { + this.experiment.inputColumns = []; + for (let i = 0; i < this.dataset?.columnInfo.length; i++) { + this.experiment.inputColumns.push(this.dataset.columnInfo[i].columnName); + } + } + } + resetOutputColumn() { + this.experiment.outputColumn = this.experiment.inputColumns[0]; + } + + setDeleteRowsForMissingValTreatment() { + if (this.experiment != undefined) { + this.experiment.nullValues = NullValueOptions.DeleteRows; + this.experiment.nullValuesReplacers = []; + for (let i = 0; i < this.experiment.inputColumns.length; i++) { + this.experiment.nullValuesReplacers.push({ + column: this.experiment.inputColumns[i], + option: NullValueOptions.DeleteRows, + value: "" + }); + } + } + } + + columnTableChangeDetected() { + this.columnTableChanged.emit(); + } + + columnTypeChanged(columnName: string) { + if (this.experiment.outputColumn == columnName) + this.changeOutputColumn(columnName); + else + this.columnTableChangeDetected(); + } + + changeInputColumns(targetMatCheckbox: MatCheckboxChange, columnName: string) { + if (this.experiment != undefined) { + + if (targetMatCheckbox.checked) { + if (this.experiment.inputColumns.filter(x => x == columnName)[0] == undefined) { + this.experiment.inputColumns.push(columnName); + } + } + else { + this.experiment.inputColumns = this.experiment.inputColumns.filter(x => x != columnName); + //console.log("Input columns: ", this.experiment.inputColumns); + //TODO: da se zatamni kolona koja je unchecked + //this.experiment.encodings = this.experiment.encodings.filter(x => x.columnName != columnName); samo na kraju iz enkodinga skloni necekirane + this.experiment.nullValuesReplacers = this.experiment.nullValuesReplacers.filter(x => x.column != columnName); + if (columnName == this.experiment.outputColumn) + this.experiment.outputColumn = this.experiment.inputColumns[0]; + } + this.columnTableChangeDetected(); + } + } + + changeOutputColumn(columnName: string) { + if (this.experiment != undefined && this.dataset != undefined) { + let i = this.dataset.columnInfo.findIndex(x => x.columnName == this.experiment!.outputColumn); + if (this.experiment.columnTypes[i] == ColumnType.numerical) { + this.experiment.type = ProblemType.Regression; + } + else { + if (this.dataset.columnInfo[i].uniqueValues!.length == 2) + this.experiment.type = ProblemType.BinaryClassification; + else + this.experiment.type = ProblemType.MultiClassification; + } + this.columnTableChangeDetected(); + } + } + + resetColumnEncodings(encodingType: Encoding) { + if (this.experiment != undefined && this.dataset != undefined) { + this.experiment.encodings = []; + for (let i = 0; i < this.dataset?.columnInfo.length; i++) { + this.experiment.encodings.push(new ColumnEncoding(this.dataset?.columnInfo[i].columnName, encodingType)); + //console.log(this.experiment.encodings); + } + this.columnTableChangeDetected(); + } + } + openEncodingDialog() { + const dialogRef = this.dialog.open(EncodingDialogComponent, { + width: '300px' + }); + dialogRef.afterClosed().subscribe(selectedEncoding => { + if (selectedEncoding != undefined) + this.resetColumnEncodings(selectedEncoding); + }); + } + + resetMissingValuesTreatment(selectedMissingValuesOption: NullValueOptions) { + if (this.experiment != undefined && this.dataset != undefined) { + + if (selectedMissingValuesOption == NullValueOptions.DeleteColumns) { + this.experiment.nullValues = NullValueOptions.DeleteColumns; + this.experiment.nullValuesReplacers = []; + for (let i = 0; i < this.experiment.inputColumns.length; i++) { + this.experiment.nullValuesReplacers.push({ + column: this.experiment.inputColumns[i], + option: NullValueOptions.DeleteColumns, + value: "" + }); + this.nullValOption[i] = "Obriši kolonu"; + } + } + else if (selectedMissingValuesOption == NullValueOptions.DeleteRows) { + this.experiment.nullValues = NullValueOptions.DeleteRows; + this.experiment.nullValuesReplacers = []; + for (let i = 0; i < this.experiment.inputColumns.length; i++) { + this.experiment.nullValuesReplacers.push({ + column: this.experiment.inputColumns[i], + option: NullValueOptions.DeleteRows, + value: "" + }); + let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[i])[0]).numNulls; + this.nullValOption[i] = "Obriši redove (" + numOfRowsToDelete + ")"; + } + } + this.columnTableChangeDetected(); + } + } + openMissingValuesDialog() { + const dialogRef = this.dialog.open(MissingvaluesDialogComponent, { + width: '400px' + }); + dialogRef.afterClosed().subscribe(selectedMissingValuesOption => { + if (selectedMissingValuesOption != undefined) + this.resetMissingValuesTreatment(selectedMissingValuesOption); + }); + } + + openSaveExperimentDialog() { + const dialogRef = this.dialog.open(SaveExperimentDialogComponent, { + width: '400px' + }); + dialogRef.afterClosed().subscribe(selectedName => { + this.experiment.name = selectedName; + //napravi odvojene dugmice za save i update -> za update nece da se otvara dijalog za ime + this.experimentService.addExperiment(this.experiment).subscribe((response) => { + this.experiment._id = response._id; + this.okPressed.emit(); + }); + }); + } + + openUpdateExperimentDialog() { + this.experimentService.updateExperiment(this.experiment).subscribe((response) => { + this.experiment = response; + Shared.openDialog("Izmena eksperimenta", "Uspešno ste izmenili podatke o eksperimentu."); + }); + } + + MissValsDeleteClicked(event: Event, replacementType: NullValueOptions, index: number) { + if (this.experiment != undefined && this.dataset != undefined) { + let columnName = (<HTMLInputElement>event.currentTarget).value; + let arrayElement = this.experiment.nullValuesReplacers.filter(x => x.column == columnName)[0]; + + if (arrayElement == undefined) { + this.experiment.nullValuesReplacers.push({ + column: columnName, + option: (replacementType == NullValueOptions.DeleteColumns) ? NullValueOptions.DeleteColumns : NullValueOptions.DeleteRows, + value: "" + }); + } + else { + arrayElement.option = (replacementType == NullValueOptions.DeleteColumns) ? NullValueOptions.DeleteColumns : NullValueOptions.DeleteRows; + arrayElement.value = ""; + } + + let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[index])[0]).numNulls; + this.nullValOption[index] = (replacementType == NullValueOptions.DeleteColumns) ? "Obriši kolonu" : "Obriši redove (" + numOfRowsToDelete + ")"; + this.columnTableChangeDetected(); + } + } + + MissValsReplaceClicked(event: Event, columnName: string, index: number) { + if (this.experiment != undefined) { + let fillValue = (<HTMLInputElement>event.currentTarget).value; + let arrayElement = this.experiment.nullValuesReplacers.filter(x => x.column == columnName)[0]; + + if (arrayElement == undefined) { + this.experiment.nullValuesReplacers.push({ + column: columnName, + option: NullValueOptions.Replace, + value: fillValue + }); + } + else { + arrayElement.option = NullValueOptions.Replace; + arrayElement.value = fillValue; + } + + this.nullValOption[index] = "Popuni sa: " + fillValue; + this.columnTableChangeDetected(); + } + } + getValue(columnName: string): string { + if (<HTMLInputElement>document.getElementById(columnName) != undefined) + return (<HTMLInputElement>document.getElementById(columnName)).value; + return '0'; + } + saveExperiment() { + this.openSaveExperimentDialog(); + } + updateExperiment() { + this.openUpdateExperimentDialog(); + } + + + tabs = [ + new Tab(0, 'Podešavanja kolona', Table.Columns), + new Tab(1, 'Podaci', Table.Data), + new Tab(2, 'Korelaciona matrica', Table.CorrelationMatrix) + ] + + selectedTab: Tab = this.tabs[0]; + hoveringOverTab: (Tab | null) = null; + + tabToDisplay: Table = Table.Columns; + + selectTab(index: number) { + this.selectedTab = this.tabs[index]; + this.tabToDisplay = this.tabs[index].value; + } + + hoverOverTab(index: number) { + if (index < 0) { + this.hoveringOverTab = null; + this.tabToDisplay = this.selectedTab.value; + } else { + this.hoveringOverTab = this.tabs[index]; + this.tabToDisplay = this.tabs[index].value; + } + } + + calcZIndex(i: number) { + let zIndex = (this.tabs.length - i - 1) + if (this.selectedTab.index == i) + zIndex = this.tabs.length + 1; + if (this.hoveringOverTab?.index == i) + zIndex = this.tabs.length + 2; + return zIndex; + } + + Table = Table; +} + +export enum Table { + Columns, + Data, + CorrelationMatrix +} + +export class Tab { + constructor( + public index: number, + public name: string, + public value: Table + ) { } +} diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.css b/frontend/src/app/_elements/dataset-load/dataset-load.component.css deleted file mode 100644 index ff6e2750..00000000 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.css +++ /dev/null @@ -1,18 +0,0 @@ -.btnType1 { - background-color: #003459; - color: white; - padding-top: 2vh; - padding-bottom: 2vh; -} -.btnType2 { - background-color: white; - color: #003459; - border-color: #003459; - padding-top: 2vh; - padding-bottom: 2vh; - -} -.selectedDatasetClass { - /*border-color: 2px solid #003459;*/ - background-color: lightblue; -}
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.html b/frontend/src/app/_elements/dataset-load/dataset-load.component.html deleted file mode 100644 index f244a882..00000000 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html +++ /dev/null @@ -1,34 +0,0 @@ -<div> - - <!--Sklonjeno ucitavanje novog dataseta i sve opcije u vezi sa tim, premesteno u add-new-dataset--> - - <div class="d-flex flex-row justify-content-center align-items-center mt-3 mb-5"> - <button type="button" id="btnMyDataset" class="btn" (click)="viewMyDatasetsForm()" [ngClass]="{'btnType1': showMyDatasets, 'btnType2': !showMyDatasets}"> - Izaberite dataset iz kolekcije - </button> - <h3 class="mt-3 mx-3">ili</h3> - <button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()" [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}"> - Dodajte novi dataset - </button> - </div> - - <div class="px-5 my-2"> - <input *ngIf="showMyDatasets" type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> - </div> - <div class="px-5" *ngIf="showMyDatasets"> - <div class="overflow-auto" style="max-height: 500px;"> - <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let dataset of myDatasets|filter:term" [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}"> - <app-item-dataset name="usersDataset" [dataset]="dataset" (click)="selectThisDataset(dataset);"></app-item-dataset> - </li> - </ul> - </div> - <div class="px-5 mt-5"> - <app-datatable [tableData]="tableData"></app-datatable> - </div> - </div> - - <app-add-new-dataset [style]="(showMyDatasets)?'display:none;visibility:hidden;':''" id="dataset"> - </app-add-new-dataset> - -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts index be1dc097..73dbf2d2 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -8,7 +8,6 @@ import { DatasetsService } from 'src/app/_services/datasets.service'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; import { Output, EventEmitter } from '@angular/core'; import { SignalRService } from 'src/app/_services/signal-r.service'; -import { AuthService } from 'src/app/_services/auth.service'; @Component({ selector: 'app-dataset-load', @@ -34,15 +33,7 @@ export class DatasetLoadComponent implements OnInit { term: string = ""; - constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService, private authService: AuthService) { - this.fetchDatasets(); - - authService.loggedInEvent.subscribe(_ => { - this.fetchDatasets(); - }) - } - - fetchDatasets() { + constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService) { this.datasets.getMyDatasets().subscribe((datasets) => { this.myDatasets = datasets; }); @@ -61,6 +52,13 @@ export class DatasetLoadComponent implements OnInit { //this.resetCbsAndRbs(); //TREBA DA SE DESI } + refreshMyDatasets() { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets; + this.showMyDatasets = true; + }); + } + selectThisDataset(dataset: Dataset) { this.selectedDataset = dataset; this.selectedDatasetLoaded = false; @@ -90,19 +88,10 @@ export class DatasetLoadComponent implements OnInit { return true; } - refreshMyDatasets(selectedDatasetId: string | null) { - this.datasets.getMyDatasets().subscribe((datasets) => { - this.myDatasets = datasets.reverse(); - this.showMyDatasets = true; - this.selectedDataset = this.myDatasets.filter(x => x._id == selectedDatasetId)[0]; - this.resetSelectedDataset(); - }); - } - ngOnInit(): void { if (this.signalRService.hubConnection) { - this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => { - this.refreshMyDatasets(dId); + this.signalRService.hubConnection.on("NotifyDataset", _ => { + this.refreshMyDatasets(); }); } else { console.warn("Dataset-Load: No connection!"); diff --git a/frontend/src/app/_elements/datatable/datatable.component.html b/frontend/src/app/_elements/datatable/datatable.component.html index 8db62aff..27d66dd3 100644 --- a/frontend/src/app/_elements/datatable/datatable.component.html +++ b/frontend/src/app/_elements/datatable/datatable.component.html @@ -1,18 +1,11 @@ -<div *ngIf="tableData.hasInput"> - <div> - <div *ngIf="!tableData.loaded" backgroundColor="secondary" style="width: 100%; height: 100%;" - class="d-flex justify-content-center align-items-center"> +<div *ngIf="tableData.hasInput" class="position-relative"> + <div class="text-white"> + <div *ngIf="!tableData.loaded" style="width: 100%; height: 100%;" class="d-flex justify-content-center align-items-center"> <app-loading></app-loading> </div> <div *ngIf="tableData.loaded && tableData.data"> - <div id="info" *ngIf="tableData.data.length > 0 && tableData.data[0].length > 0" - class="d-flex flex-row justify-content-center align-items-center"> - <div class="fs-5 mb-3"> - Tabela {{tableData.numCols}}x{{tableData.numRows}} - </div> - </div> - <div class="table-responsive" style="overflow: auto; border-radius: 5px;"> - <table *ngIf="tableData.data.length > 0 && tableData.hasHeader && tableData.data[0].length > 0" class="table table-bordered table-light"> + <div style="border-radius: 5px; overflow-x: auto; overflow-y: hidden;"> + <table *ngIf="tableData.data.length && tableData.data[0].length > 0" class="table table-responsive table-sm text-offwhite row-height"> <thead> <tr> <th *ngFor="let item of tableData.data[0]; let i = index">{{item}}</th> @@ -22,22 +15,23 @@ <tr *ngFor="let row of tableData.data | slice:1"> <td *ngFor="let col of row">{{col}}</td> </tr> - <tr> - <td colspan="100" class="text-lg-center fs-6">+ {{tableData.numRows - 11}} redova...</td> - </tr> - </tbody> - </table> - <table *ngIf="tableData.data.length > 0 && !tableData.hasHeader && tableData.data[0].length > 0" class="table table-bordered table-light"> - <tbody> - <tr *ngFor="let row of tableData.data"> - <td *ngFor="let col of row">{{col}}</td> - </tr> - <tr> - <td colspan="100" class="text-lg-center fs-6">+ {{tableData.numRows - 10}} redova...</td> - </tr> </tbody> </table> </div> + <div class="row" > + <div id="info" *ngIf="tableData.data.length > 0 && tableData.data[0].length > 0" class="d-flex flex-row justify-content-right align-items-right col-sm"> + <div class=" mb-3"> + Tabela {{tableData.numCols}} x {{tableData.numRows}} + </div> + </div> + <div class="col-sm"></div> + <div class="col-sm"></div> + <div class="footer-center col-sm">Prikazano 10/{{tableData.numRows}} redova</div> + <div class="col-sm"></div> + <div class="col-sm"></div> + <div class="col-sm"></div> + + </div> </div> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/datatable/datatable.component.ts b/frontend/src/app/_elements/datatable/datatable.component.ts index 82374f4d..560a1c21 100644 --- a/frontend/src/app/_elements/datatable/datatable.component.ts +++ b/frontend/src/app/_elements/datatable/datatable.component.ts @@ -18,7 +18,6 @@ export class DatatableComponent implements OnInit { export class TableData { constructor( - public hasHeader = true, public hasInput = false, public loaded = false, public numRows = 0, diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css new file mode 100644 index 00000000..62324d62 --- /dev/null +++ b/frontend/src/app/_elements/folder/folder.component.css @@ -0,0 +1,213 @@ +#folder { + /*position: absolute; + left: 50%; + transform: translateX(-50%);*/ +} + +#tabs { + display: flex; + flex-direction: row; + align-items: flex-end; + height: 3.1rem; +} + +#tabs>.folder-tab:not(:first-child) { + margin-left: -5px; +} + +.folder-tab { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + position: relative; + 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; +} + +.folder-tab:not(:first-child) { + margin-block-start: auto; +} + +.selected-tab { + height: 3rem; + background-color: var(--ns-primary); + color: var(--offwhite); +} + +.hover-tab { + height: 3.2rem; +} + +.selected-tab, +.hover-tab { + width: fit-content !important; +} + +.tab-link { + color: var(--offwhite) !important; + text-decoration: none !important; + cursor: pointer; + padding: 0.5rem; +} + +.tab-link:active { + text-decoration: underline !important; +} + +.selected-tab { + background-color: var(--ns-primary); +} + +#searchbar { + height: 2.5rem; + background-color: var(--ns-bg-dark-100); + border-bottom: 1px solid var(--ns-primary); + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + width: 100%; +} + +.collapse-horizontal { + white-space: nowrap; + height: 2.5rem; + overflow-x: hidden; +} + +#search-options { + margin-left: auto; + display: flex; + flex-direction: row; + align-items: center; + height: 100%; +} + +#selected-content { + background-color: var(--ns-bg-dark-50); + width: 100%; + /*backdrop-filter: blur(2px);*/ + border-color: var(--ns-primary); + border-style: solid; + border-width: 1px 1px 1px 1px; + border-top-right-radius: 4px; +} + +#footer { + display: flex; + flex-direction: row; + justify-content: center; +} + +.bottom-button { + font-size: large; + position: relative; + background-color: var(--ns-primary); + width: 10rem; + height: 2.3rem; + border-color: var(--ns-primary); + border-style: solid; + border-width: 0px 1px 1px 1px; +} + +.rounded-bottom { + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.separator { + border-left-color: var(--ns-primary); + border-left-width: 1px; + border-left-style: solid; +} + +.list-view { + height: 100%; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.list-item { + height: 3rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--ns-primary); + flex-shrink: 0; +} + +.list-item:hover { + background-color: var(--ns-bg-dark-100); + box-shadow: 0px 3px 3px var(--ns-primary); +} + +.list-item:hover>.hover-hide { + display: none; +} + +.hover-show { + display: none; +} + +.list-item:hover>.hover-show { + display: initial; +} + +.list-add { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex-grow: 1; + height: 100%; +} + +.folder-inside { + width: 100%; + height: 40rem; + overflow-y: auto; +} + +.file-content { + width: 100%; + height: 100%; + position: relative; +} + +.file-bottom-buttons { + position: absolute; + bottom: 15px; + right: 15px; + display: flex; + flex-direction: row-reverse; +} + +.file-button { + position: relative; + color: var(--offwhite); + border-radius: 4px; + border: 1px solid var(--ns-primary); + margin: 5px; + padding: 5px; + cursor: pointer; + z-index: 1001; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.file-button:hover { + background-color: var(--ns-primary); +} + +.form-hidden { + display: none; +}
\ 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 new file mode 100644 index 00000000..bff066be --- /dev/null +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -0,0 +1,111 @@ +<div id="folder"> + <div id="tabs"> + <div id="new-file-tab" *ngIf="newFile" class="folder-tab p-1 rounded-top" [style]="'z-index:' + (selectedTab == TabType.NewFile ? 11 : 10) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.NewFile, 'hover-tab' : hoverTab == TabType.NewFile}"> + <mat-icon class="text-offwhite">add</mat-icon> + <a class="stretched-link tab-link" (click)="selectTab(TabType.NewFile)" (mouseenter)="hoverOverTab(TabType.NewFile)" (mouseleave)="hoverOverTab(TabType.None)"> + {{newFile.name}} + </a> + </div> + <!--<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 class="folder-tab p-1 rounded-top" *ngFor="let tab of tabsToShow; let i = index" [style]="'z-index:' + (selectedTab == tab ? 11 : (tabsToShow.length - i)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == tab, 'hover-tab' : hoverTab == tab}"> + <a class="m-1 stretched-link tab-link" (click)="selectTab(tab)" (mouseenter)="hoverOverTab(tab)" (mouseleave)="hoverOverTab(TabType.None)">{{tabTitles[tab]}}</a> + </div> + + <div class="folder-tab p-1 rounded-top" *ngIf="selectedFile" [style]="'z-index:' + (selectedTab == TabType.File ? 11 : (tabsToShow.length)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.File, 'hover-tab' : hoverTab == TabType.File}"> + <a class="m-1 stretched-link tab-link" (click)="selectTab(TabType.File)" (mouseenter)="hoverOverTab(TabType.File)" (mouseleave)="hoverOverTab(TabType.None)">{{selectedFile.name}}</a> + </div> + </div> + <div id="selected-content" class="rounded-bottom text-offwhite"> + <div id="searchbar" *ngIf="listView"> + <!-- <div id="path" class="ps-2">{{folderName}} + </div> + <mat-icon>keyboard_arrow_right</mat-icon> --> + <div id="search" class="text-offwhite mx-1"> + <mat-form-field> + <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-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> --> + <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"> + [sort options here TODO] + </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()"> + <mat-icon>view_list</mat-icon> + </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)"> + <mat-icon>delete</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"></app-form-model> + <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment"></app-form-dataset> + </div> + <div [ngClass]="{'form-hidden' : !listView}" class="list-view"> + <div *ngFor="let file of filteredFiles; let i = index" class="list-item force-link" (click)="selectFile(file)"> + <div class="mx-2"> + {{file.name}} + </div> + <div class="mx-2 hover-hide"> + {{file.lastUpdated | date}} + </div> + <div class="mx-2 hover-show" *ngIf="selectedTab !== TabType.PublicDatasets && selectedTab !== TabType.PublicModels"> + <button class="btn-clear file-button" (click)="deleteFile(file, $event)"> + <mat-icon>delete</mat-icon> + </button> + </div> + + </div> + <div class="list-add" [ngSwitch]="type"> + <button mat-raised-button *ngSwitchCase="FolderType.Dataset" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javni ' : ' '}}izvor podataka</button> + <button mat-raised-button *ngSwitchCase="FolderType.Model" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javnu ' : ' '}}konfiguraciju neuronske mreže</button> + <button mat-raised-button *ngSwitchCase="FolderType.Experiment" routerLink="/experiment">Dodaj eksperiment</button> + </div> + </div> + </div> + </div> + <div id="footer" [ngSwitch]="newFileSelected" *ngIf="!listView"> + <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> + </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> + </button> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/carousel/carousel.component.spec.ts b/frontend/src/app/_elements/folder/folder.component.spec.ts index 9196e044..33a573a7 100644 --- a/frontend/src/app/_elements/carousel/carousel.component.spec.ts +++ b/frontend/src/app/_elements/folder/folder.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CarouselComponent } from './carousel.component'; +import { FolderComponent } from './folder.component'; -describe('CarouselComponent', () => { - let component: CarouselComponent; - let fixture: ComponentFixture<CarouselComponent>; +describe('FolderComponent', () => { + let component: FolderComponent; + let fixture: ComponentFixture<FolderComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ CarouselComponent ] + declarations: [ FolderComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(CarouselComponent); + fixture = TestBed.createComponent(FolderComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts new file mode 100644 index 00000000..fabb524c --- /dev/null +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -0,0 +1,379 @@ +import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import { FolderFile, FolderType } from 'src/app/_data/FolderFile'; +import Model from 'src/app/_data/Model'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import Shared from 'src/app/Shared'; +import { ModelsService } from 'src/app/_services/models.service'; +import { FormDatasetComponent } from '../form-dataset/form-dataset.component'; +import Experiment from 'src/app/_data/Experiment'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; +import { PredictorsService } from 'src/app/_services/predictors.service'; +import { SignalRService } from 'src/app/_services/signal-r.service'; +import { FormModelComponent } from '../form-model/form-model.component'; + +@Component({ + selector: 'app-folder', + templateUrl: './folder.component.html', + styleUrls: ['./folder.component.css'] +}) +export class FolderComponent implements AfterViewInit { + + @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent; + @ViewChild(FormModelComponent) formModel!: FormModelComponent; + + @Input() folderName: string = 'Moji podaci'; + @Input() files!: FolderFile[] + + newFile?: Dataset | Model; + + @Input() type: FolderType = FolderType.Dataset; + @Input() forExperiment!: Experiment; + @Input() startingTab!: TabType; + + newFileSelected: boolean = true; + + selectedFileIndex: number = -1; + selectedFile?: FolderFile; + hoveringOverFileIndex: number = -1; + + fileToDisplay?: FolderFile; + + @Output() selectedFileChanged: EventEmitter<FolderFile> = new EventEmitter(); + @Output() okPressed: EventEmitter<string> = new EventEmitter(); + + searchTerm: string = ''; + + constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService) { + this.tabsToShow.forEach(tab => this.folders[tab] = []); + } + + ngAfterViewInit(): void { + this.refreshFiles(null); + + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => { + if (this.type == FolderType.Dataset) { + this.refreshFiles(dId); + } + }); + } else { + console.warn("Dataset-Load: No connection!"); + } + } + + displayFile() { + if (this.type == FolderType.Dataset) + this.formDataset.dataset = <Dataset>this.fileToDisplay; + else if (this.type == FolderType.Model) + this.formModel.newModel = <Model>this.fileToDisplay; + } + + hoverOverFile(i: number) { + /*this.hoveringOverFileIndex = i; + if (i != -1) { + this.fileToDisplay = this.files[i]; + } else { + if (this.newFileSelected) { + this.fileToDisplay = this.newFile; + } else { + this.fileToDisplay = this.files[this.selectedFileIndex]; + } + } + this.displayFile();*/ + } + + selectNewFile() { + if (!this.newFile) { + this.createNewFile(); + } + this.fileToDisplay = this.newFile; + this.newFileSelected = true; + this.listView = false; + this.displayFile(); + if(this.type == FolderType.Dataset) + this.formDataset.clear(); + } + + selectFile(file?: FolderFile) { + this.selectedFile = file; + this.fileToDisplay = file; + this.newFileSelected = false; + this.listView = false; + this.selectedFileChanged.emit(this.selectedFile); + this.selectTab(TabType.File); + this.displayFile(); + + if(this.type == FolderType.Dataset) + this.formDataset.loadExisting(); + } + + createNewFile() { + if (this.type == FolderType.Dataset) { + this.newFile = new Dataset(); + } else if (this.type == FolderType.Model) { + this.newFile = new Model(); + } + } + + ok() { + this.okPressed.emit(); + } + + _initialized: boolean = false; + + refreshFiles(selectedDatasetId: string | null) { + this.files = [] + this.filteredFiles.length = 0; + this.folders[TabType.NewFile] = []; + this.folders[TabType.File] = []; + this.tabsToShow.forEach(tab => { + this.folders[tab] = []; + }); + + this.datasetsService.getMyDatasets().subscribe((datasets) => { + this.folders[TabType.MyDatasets] = datasets; + if (selectedDatasetId) { + this.selectFile(datasets.filter(x => x._id == selectedDatasetId)[0]); + } + }); + + this.experimentsService.getMyExperiments().subscribe((experiments) => { + this.folders[TabType.MyExperiments] = experiments; + }); + + this.datasetsService.getPublicDatasets().subscribe((datasets) => { + this.folders[TabType.PublicDatasets] = datasets; + }); + + this.modelsService.getMyModels().subscribe((models) => { + this.folders[TabType.MyModels] = models; + }); + + /*this.modelsService.getMyModels().subscribe((models) => { + this.folders[TabType.PublicModels] = models; + });*/ + this.folders[TabType.PublicModels] = []; + + this.experimentsService.getMyExperiments().subscribe((experiments) => { + this.folders[TabType.MyExperiments] = experiments; + }); + + if (!this._initialized) { + this.files = this.folders[this.startingTab]; + this.filteredFiles = []; + this.selectTab(this.startingTab); + this._initialized = true; + } + + this.searchTermsChanged(); + } + + saveNewFile() { + switch (this.type) { + case FolderType.Dataset: + this.formDataset!.uploadDataset((dataset: Dataset) => { + this.newFile = undefined; + Shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira."); + this.refreshFiles(null); + }, + () => { + Shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); + }); + break; + case FolderType.Model: + this.modelsService.addModel(this.formModel.newModel).subscribe(model => { + this.newFile = undefined; + Shared.openDialog("Obaveštenje", "Uspešno ste dodali novu konfiguraciju neuronske mreže u kolekciju."); + this.refreshFiles(null); // todo select model + }, (err) => { + Shared.openDialog("Neuspeo pokušaj!", "Konfiguracija neuronske mreže sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeću konfiguraciju."); + }); + break; + } + } + + + /*calcZIndex(i: number) { + let zIndex = (this.files.length - i - 1) + if (this.selectedFileIndex == i) + zIndex = this.files.length + 2; + if (this.hoveringOverFileIndex == i) + zIndex = this.files.length + 3; + return zIndex; + } + + newFileZIndex() { + return (this.files.length + 1); + }*/ + + clearSearchTerm() { + this.searchTerm = ''; + this.searchTermsChanged(); + } + + filteredFiles: FolderFile[] = []; + + searchTermsChanged() { + this.filteredFiles.length = 0; + if (!this.files) return; + this.filteredFiles.push(...this.files.filter((file) => file.name.toLowerCase().includes(this.searchTerm.toLowerCase()))); + /*if (this.selectedFile) { + if (!this.filteredFiles.includes(this.selectedFile)) { + if (this.hoverTab === TabType.None && this.getFolderType(this.selectedTab) === this.type) { + this.selectFile(undefined); + console.log(this.getFolderType(this.selectedTab), this.type); + } + } else { + //this.selectedFileIndex = this.filteredFiles.indexOf(this.selectedFile); + } + }*/ + } + + listView: boolean = true; + + toggleListView() { + this.listView = !this.listView; + } + + deleteFile(file: FolderFile, event: Event) { + event.stopPropagation(); + //console.log('delete'); + switch (this.type) { + case FolderType.Dataset: + this.datasetsService.deleteDataset(<Dataset>file).subscribe((response) => { + this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1); + this.refreshFiles(null); + }); + break; + case FolderType.Model: + this.modelsService.deleteModel(<Model>file).subscribe((response) => { + this.refreshFiles(null); + }); + break; + case FolderType.Experiment: + // this.experimentsService.deleteExperiment(<Model>file).subscribe((response) => { + // console.log(response); + // }); + //todo delete za predictor + break; + } + } + + folders: { [tab: number]: FolderFile[] } = {}; + + tabTitles: { [tab: number]: string } = { + [TabType.File]: 'Fajl', + [TabType.NewFile]: 'Novi fajl', + [TabType.MyDatasets]: 'Moji izvori podataka', + [TabType.PublicDatasets]: 'Javni izvori podataka', + [TabType.MyModels]: 'Moje konfiguracije neuronske mreže', + [TabType.PublicModels]: 'Javne konfiguracije neuronske mreže', + [TabType.MyExperiments]: 'Eksperimenti', + }; + + FolderType = FolderType; + Privacy = Privacy; + TabType = TabType; + + privacy: Privacy = Privacy.Private; + + @Input() tabsToShow: TabType[] = [ + TabType.MyDatasets, + TabType.PublicDatasets, + TabType.MyModels, + TabType.PublicModels, + TabType.MyExperiments + ] + + @Input() selectedTab: TabType = TabType.NewFile; + hoverTab: TabType = TabType.None; + + selectTab(tab: TabType) { + setTimeout(() => { + if (tab == TabType.NewFile) { + this.selectNewFile(); + } + + this.listView = this.getListView(tab); + this.type = this.getFolderType(tab); + this.privacy = this.getPrivacy(tab); + this.selectedTab = tab; + this.files = this.folders[tab]; + + if (tab !== TabType.File && tab !== TabType.NewFile) + this.searchTermsChanged(); + }); + } + + getListView(tab: TabType) { + switch (tab) { + case TabType.File: + case TabType.NewFile: + case TabType.None: + return false; + case TabType.MyExperiments: + case TabType.MyDatasets: + case TabType.MyModels: + case TabType.PublicDatasets: + case TabType.PublicModels: + return true; + default: + return false; + } + } + + getFolderType(tab: TabType) { + switch (tab) { + case TabType.MyExperiments: + return FolderType.Experiment; + case TabType.MyDatasets: + case TabType.PublicDatasets: + return FolderType.Dataset; + case TabType.MyModels: + case TabType.PublicModels: + return FolderType.Model; + default: + return this.type; + } + } + + getPrivacy(tab: TabType) { + switch (tab) { + case TabType.PublicDatasets: + case TabType.PublicModels: + return Privacy.Public; + default: + return Privacy.Private; + } + } + + hoverOverTab(tab: TabType) { + this.listView = this.getListView(tab); + this.privacy = this.getPrivacy(tab); + this.hoverTab = tab; + if (tab == TabType.None) { + this.listView = this.getListView(this.selectedTab); + this.files = this.folders[this.selectedTab]; + } else { + this.files = this.folders[tab]; + } + this.searchTermsChanged(); + } +} + +export enum Privacy { + Private, + Public +} + +export enum TabType { + NewFile, + File, + MyDatasets, + PublicDatasets, + MyModels, + PublicModels, + MyExperiments, + None +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.css b/frontend/src/app/_elements/form-dataset/form-dataset.component.css new file mode 100644 index 00000000..953daa0c --- /dev/null +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.css @@ -0,0 +1,64 @@ +.folderBox { + width: 100%; + height: 100%; + position: relative; +} + +.topBar { + width: 100%; + margin: 1rem; + align-items: flex-start; +} + +.topBar label{ + font-size: 30px; +} +.topBar mat-form-field{ + width: 250px; +} + +.toptop{ + margin-left: 1.5%; + width: 50%; +} + +.fileButton{ + margin-top: 10px; +} + +.file-container { + border: 4px solid transparent; + position: relative; + margin-left: 3%; + width: 94%; + min-height: 400px; + height: 95%; +} + +.dottedClass { + border: 4px dotted white; + border-radius: 25px; +} + +.icon-display { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%) scale(4); +} + +.hidden { + visibility: hidden; +} + +.file { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; +} + +.file-container input{ + border-radius: 5px; + left: 0%; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html new file mode 100644 index 00000000..281f9c05 --- /dev/null +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html @@ -0,0 +1,65 @@ +<div class="folderBox" *ngIf="dataset"> + + <div class="row" style="margin-right: 0;"> + <div class="topBar"> + <div class="row toptop"> + <div class="col-sm mb-3"> + <div class="fileButton"> + <button type="button" mat-raised-button (click)="fileInput.click()">Dodaj izvor podataka</button> + + </div> + </div> + + <div class="col-sm"> + <div role="group"> + <div class="row"> + <mat-form-field class="example-full-width" appearance="fill"> + <mat-label>Naziv</mat-label> + <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name"> + + + <mat-error *ngIf="nameFormControl.hasError('required')"> + Naziv je <strong>obavezan</strong> + </mat-error> + </mat-form-field> + </div> + </div> + </div> + <div class="col-sm"> + <mat-form-field appearance="fill"> + <mat-label>Delimiter</mat-label> + <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (change)="update()" value=","> + <mat-option *ngFor="let option of delimiterOptions" [value]="option"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="col-sm"> + + </div> + </div> + <div class="row" *ngIf="firstInput"> + <label class=" mt-5">{{filename}}</label> + + </div> + </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"> + + <div class="mt-5 datatable"> + <app-datatable [tableData]="tableData"></app-datatable> + </div> + + + </div> + </div> + +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.spec.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.spec.ts index 5601b57b..51491c58 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.spec.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { DatasetLoadComponent } from './dataset-load.component'; +import { FormDatasetComponent } from './form-dataset.component'; -describe('DatasetLoadComponent', () => { - let component: DatasetLoadComponent; - let fixture: ComponentFixture<DatasetLoadComponent>; +describe('FormDatasetComponent', () => { + let component: FormDatasetComponent; + let fixture: ComponentFixture<FormDatasetComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ DatasetLoadComponent ] + declarations: [ FormDatasetComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(DatasetLoadComponent); + fixture = TestBed.createComponent(FormDatasetComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts index 1f395105..1eed2cdc 100644 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -1,38 +1,50 @@ -import { Component, EventEmitter, Output, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import Dataset from 'src/app/_data/Dataset'; import { DatasetsService } from 'src/app/_services/datasets.service'; import { ModelsService } from 'src/app/_services/models.service'; import shared from 'src/app/Shared'; import { DatatableComponent, TableData } from '../datatable/datatable.component'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; +import { FormControl, Validators } from '@angular/forms'; @Component({ - selector: 'app-add-new-dataset', - templateUrl: './add-new-dataset.component.html', - styleUrls: ['./add-new-dataset.component.css'] + selector: 'app-form-dataset', + templateUrl: './form-dataset.component.html', + styleUrls: ['./form-dataset.component.css'] }) -export class AddNewDatasetComponent { +export class FormDatasetComponent { @ViewChild(DatatableComponent) datatable!: DatatableComponent; - delimiterOptions: Array<string> = [",", ";", "|", "razmak", "novi red"]; //podrazumevano "," + nameFormControl = new FormControl('', [Validators.required, Validators.email]); + + delimiterOptions: Array<string> = [",", ";", "|", "razmak", "novi red"]; //podrazumevano "," csvRecords: any[] = []; files: File[] = []; rowsNumber: number = 0; colsNumber: number = 0; - dataset: Dataset; //dodaj ! potencijalno + @Input() dataset: Dataset; //dodaj ! potencijalno tableData: TableData = new TableData(); + @ViewChild('fileInput') fileInput!: ElementRef + + filename: String; + constructor(private modelsService: ModelsService, private datasetsService: DatasetsService, private csv: CsvParseService) { this.dataset = new Dataset(); this.dataset.delimiter = ','; + this.filename = ""; } //@ViewChild('fileImportInput', { static: false }) fileImportInput: any; cemu je ovo sluzilo? + clear(){ + this.tableData.hasInput = false; + } + changeListener($event: any): void { this.files = $event.srcElement.files; if (this.files.length == 0 || this.files[0] == null) { @@ -42,12 +54,17 @@ export class AddNewDatasetComponent { else this.tableData.hasInput = true; + this.filename = this.files[0].name; this.tableData.loaded = false; this.update(); } + firstInput = false; + update() { + this.firstInput = true + if (this.files.length < 1) return; @@ -56,48 +73,73 @@ export class AddNewDatasetComponent { if (typeof fileReader.result === 'string') { const result = this.csv.csvToArray(fileReader.result, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter) - if (this.dataset.hasHeader) - this.csvRecords = result.splice(0, 11); - else - this.csvRecords = result.splice(0, 10); + + this.csvRecords = result.splice(0, 11); this.colsNumber = result[0].length; this.rowsNumber = result.length; - this.tableData.data = this.csvRecords - this.tableData.hasHeader = this.dataset.hasHeader; + this.tableData.data = this.csvRecords; this.tableData.loaded = true; this.tableData.numCols = this.colsNumber; this.tableData.numRows = this.rowsNumber; } } fileReader.readAsText(this.files[0]); + + this.dataset.name = this.filename.slice(0, this.filename.length - 4); + } + + loadExisting(){ + this.firstInput = false; + + this.tableData.hasInput = true; + this.tableData.loaded = false; + + this.datasetsService.getDatasetFile(this.dataset.fileId).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(file, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "") ? "," : this.dataset.delimiter); + + } + }); + + } + /*exportAsXLSX():void { + this.excelService.exportAsExcelFile(this.data, 'sample'); + }*/ + checkAccessible() { if (this.dataset.isPublic) this.dataset.accessibleByLink = true; } - uploadDataset() { + uploadDataset(onSuccess: Function = (dataset: Dataset) => { }, onError: Function = () => { }) { if (this.files[0] == undefined) { shared.openDialog("Greška", "Niste izabrali fajl za učitavanje."); return; } - this.modelsService.uploadData(this.files[0]).subscribe((file) => { + return this.modelsService.uploadData(this.files[0]).subscribe((file) => { //console.log('ADD MODEL: STEP 2 - ADD DATASET WITH FILE ID ' + file._id); + this.dataset._id = ""; this.dataset.fileId = file._id; this.dataset.uploaderId = shared.userId; this.datasetsService.addDataset(this.dataset).subscribe((dataset) => { - shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira."); + onSuccess(); }, (error) => { - shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); + onError(); }); //kraj addDataset subscribe }, (error) => { - + onError(); }); //kraj uploadData subscribe } + + } diff --git a/frontend/src/app/_elements/form-model/form-model.component.css b/frontend/src/app/_elements/form-model/form-model.component.css new file mode 100644 index 00000000..11b6ef5e --- /dev/null +++ b/frontend/src/app/_elements/form-model/form-model.component.css @@ -0,0 +1,104 @@ +#container { + color: var(--offwhite); +} + +mat-label { + color: var(--offwhite) !important; +} + +select { + color: var(--offwhite) !important; +} + +mat-form-field { + color: var(--offwhite) !important; + padding: 0; + margin: 5px; + font-size: 12px; + width: 100%; +} + +hr { + color: var(--offwhite) !important; + margin-bottom: 30px; +} + +.neuron { + text-align: justify; + border: 1px solid white; + border-radius: 5px; + padding: 0; + color: var(--offwhite) !important; + background-color: var(--ns-bg-dark-100) !important; + min-width: none; + max-width: 12.5rem; +} + +.row { + margin: 0; + padding: 0; +} + +.mat-fix ::ng-deep .mat-form-field-wrapper { + margin-bottom: -1.85em; +} + +#layers-control { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +#layers { + margin: 0; + padding: 0; + display: flex; + flex-direction: row; + overflow-x: auto; + overflow-wrap: break-word; + overflow-y: hidden; + width: 100%; +} + +.layer { + border: 1px solid var(--ns-primary); + border-radius: 4px; + margin: 5px; + padding: 0px; + width: 12rem; + height: 11.1rem; +} + +.tm { + margin-left: 10px; +} + +.layer>* { + margin-top: 0; +} + +.layer>mat-form-field { + margin-left: 0; +} + +.m-2 { + max-height: 20 rem; +} + +mat-slider { + width: 50%; +} + +.slider { + background-color: transparent; +} + +.center-center { + text-align: center; + margin-right: 10px; + padding-right: 10px; + padding-bottom: 15px; + font-size: 20px !important; + font-weight: 600; +}
\ 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 new file mode 100644 index 00000000..96a5e1b6 --- /dev/null +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -0,0 +1,228 @@ +<div *ngIf="newModel"> + <div id="container"> + <div class="ns-row"> + + <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"> + </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)]="newModel.type"> + <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-1"></div> + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Optimizacija</mat-label> + <mat-select [(ngModel)]="newModel.optimizer"> + <mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + + </mat-form-field> + </div> + <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-option *ngFor="let option of Object.keys(LossFunction); let optionName of Object.values(LossFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-2"></div> + + <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-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + <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-option *ngFor="let option of Object.keys(LearningRate); let optionName of Object.values(LearningRate)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-1"></div> + + <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"> + </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-option *ngFor="let option of Object.keys(BatchSize); let optionName of Object.values(BatchSize)" [value]="option">{{option}}</mat-option> + </mat-select> + </mat-form-field> + </div> + + </div> + </div> + + <!-- GRAF --> + + <div class="m-2"> + <div class="row"> + <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px;"> + <div class="row slider rounded mb-3" style="margin-left: 10px;"> + + <div class="text-center pt-3 pb-0 mb-0"><b>{{testSetDistribution}}%</b> : <b>{{100-testSetDistribution}}%</b></div> + <div class="text-center pt-0 mt-0">Trening + <mat-slider min="10" max="90" step="10" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)"></mat-slider> + Test</div> + + </div> + <div class="row slider rounded text-offwhite justify-content-center align-items-center" style="margin-left: 10px;"> + <mat-checkbox class="pt-4 mb-3" color="accent">Nasumični redosled podataka</mat-checkbox> + </div> + + + </div> + + <div class="col-sm-9"> + <!-- {{forExperiment._columnsSelected}} --> + <app-graph [model]="newModel" *ngIf="forExperiment._columnsSelected" [inputColumns]="forExperiment.inputColumns"></app-graph> + <app-graph [model]="newModel" *ngIf="!forExperiment._columnsSelected" [inputColumns]="['Nisu odabrane ulazne kolone']"></app-graph> + </div> + </div> + </div> + + <!-- SVI LAYERI --> + + <div class="ns-row"> + + <div class="ns-col" id="layers-control"> + <div>Broj Skrivenih Slojeva</div> + <button class="btn-clear btn-icon bubble" (click)="addLayer()"> + <mat-icon>add</mat-icon> + </button> + <div>{{newModel.hiddenLayers}}</div> + <button class="btn-clear btn-icon bubble" (click)="removeLayer()"> + <mat-icon>remove</mat-icon> + </button> + + </div> + <div class="break-1"></div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Aktivaciona funkcija svih slojeva</mat-label> + + <mat-select [(ngModel)]="selectedActivation" (selectionChange)="changeAllActivation()"> + <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Broj neurona svih slojeva</mat-label> + <input matInput type="number" min="1" max="18" [(ngModel)]="selectedNumberOfNeurons" (change)="changeAllNumberOfNeurons()"> + </mat-form-field> + </div> + <div class="break-2"></div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Regularizacija svih slojeva</mat-label> + <mat-select [(ngModel)]="selectedRegularisation" (selectionChange)="changeAllRegularisation()"> + <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Stopa regularizacije svih slojeva</mat-label> + <mat-select [(ngModel)]="selectedRegularisationRate" (selectionChange)="changeAllRegularisationRate()"> + <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <!-- LAYERI --> + + <div id="layers"> + <div class="layer" *ngFor="let item of newModel.layers; let i=index"> + + + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Aktivacija</mat-label> + <button matPrefix class="btn-clear center-center text-offwhite"> + <div> + #{{i+1}} + </div> + </button> + <mat-select [(ngModel)]="newModel.layers[i].activationFunction"> + <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + + <div class="d-flex flex-row align-items-center justify-content-center tm"> + <div class="col-6" style="font-size: 13px;">Broj čvorova</div> + <button class="btn-clear btn-icon bubble" (click)="addNeuron(i)"> + <mat-icon>add</mat-icon> + </button> + <div class="col-2 text-center">{{newModel.layers[i].neurons}}</div> + <button class="btn-clear btn-icon bubble" (click)="removeNeuron(i)"> + <mat-icon>remove</mat-icon> + </button> + </div> + + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Regularizacija</mat-label> + <mat-select [(ngModel)]="newModel.layers[i].regularisation"> + <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Stopa regularizacije</mat-label> + <mat-select [(ngModel)]="newModel.layers[i].regularisationRate"> + <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/annvisual/annvisual.component.spec.ts b/frontend/src/app/_elements/form-model/form-model.component.spec.ts index cb07ef1d..af1091cc 100644 --- a/frontend/src/app/_elements/annvisual/annvisual.component.spec.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AnnvisualComponent } from './annvisual.component'; +import { FormModelComponent } from './form-model.component'; -describe('AnnvisualComponent', () => { - let component: AnnvisualComponent; - let fixture: ComponentFixture<AnnvisualComponent>; +describe('FormModelComponent', () => { + let component: FormModelComponent; + let fixture: ComponentFixture<FormModelComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ AnnvisualComponent ] + declarations: [ FormModelComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(AnnvisualComponent); + fixture = TestBed.createComponent(FormModelComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts new file mode 100644 index 00000000..71b374b0 --- /dev/null +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -0,0 +1,138 @@ +import { Component, OnInit, Input, ViewChild, Output, EventEmitter, AfterViewInit } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import Shared from 'src/app/Shared'; +import Experiment from 'src/app/_data/Experiment'; +import Model, { Layer, ActivationFunction, LossFunction, LearningRate, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType, Regularisation, RegularisationRate, BatchSize } from 'src/app/_data/Model'; +import { GraphComponent } from '../graph/graph.component'; +import { MatSliderChange } from '@angular/material/slider'; + +@Component({ + selector: 'app-form-model', + templateUrl: './form-model.component.html', + styleUrls: ['./form-model.component.css'] +}) +export class FormModelComponent implements AfterViewInit { + @ViewChild(GraphComponent) graph!: GraphComponent; + @Input() forExperiment!: Experiment; + @Output() selectedModelChangeEvent = new EventEmitter<Model>(); + testSetDistribution: number = 70; + constructor() { } + + ngAfterViewInit(): void { } + + selectFormControl = new FormControl('', Validators.required); + 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; + + selectedModel?: Model; + + ProblemType = ProblemType; + ActivationFunction = ActivationFunction; + RegularisationRate = RegularisationRate; + Regularisation = Regularisation; + metrics: any = Metrics; + LossFunction = LossFunction; + Optimizer = Optimizer; + BatchSize = BatchSize; + Object = Object; + document = document; + shared = Shared; + LearningRate = LearningRate; + Layer = Layer; + + term: string = ""; + selectedMetrics = []; + lossFunction: any = LossFunction; + + loadModel(model: Model) { + this.newModel = model; + } + + updateGraph() { + //console.log(this.newModel.layers); + this.graph.update(); + } + + removeLayer() { + if (this.newModel.hiddenLayers > 1) { + this.newModel.layers.splice(this.newModel.layers.length - 1, 1); + this.newModel.hiddenLayers -= 1; + this.updateGraph(); + } + } + addLayer() { + if (this.newModel.hiddenLayers < 128) { + this.newModel.layers.push(new Layer(this.newModel.layers.length, this.selectedActivation, this.selectedNumberOfNeurons, this.selectedRegularisation, this.selectedRegularisationRate)); + + this.newModel.hiddenLayers += 1; + this.updateGraph(); + } + + } + /* + setNeurons() + { + for(let i=0;i<this.newModel.hiddenLayers;i++){ + this.newModel.hiddenLayerNeurons[i]=1; + } + }*/ + numSequence(n: number): Array<number> { + return Array(n); + } + + removeNeuron(index: number) { + if (this.newModel.layers[index].neurons > 1) { + this.newModel.layers[index].neurons -= 1; + this.updateGraph(); + } + } + addNeuron(index: number) { + if (this.newModel.layers[index].neurons < 18) { + this.newModel.layers[index].neurons += 1; + this.updateGraph(); + } + } + selectedActivation: ActivationFunction = ActivationFunction.Sigmoid; + selectedRegularisationRate: RegularisationRate = RegularisationRate.RR1; + selectedRegularisation: Regularisation = Regularisation.L1; + selectedNumberOfNeurons: number = 3; + + changeAllActivation() { + for (let i = 0; i < this.newModel.layers.length; i++) { + this.newModel.layers[i].activationFunction = this.selectedActivation; + + } + + } + changeAllRegularisation() { + for (let i = 0; i < this.newModel.layers.length; i++) { + this.newModel.layers[i].regularisation = this.selectedRegularisation; + } + } + changeAllRegularisationRate() { + for (let i = 0; i < this.newModel.layers.length; i++) { + this.newModel.layers[i].regularisationRate = this.selectedRegularisationRate; + } + } + changeAllNumberOfNeurons() { + for (let i = 0; i < this.newModel.layers.length; i++) { + this.newModel.layers[i].neurons = this.selectedNumberOfNeurons; + this.updateGraph(); + } + } + + updateTestSet(event: MatSliderChange) { + this.testSetDistribution = event.value!; + } +} diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.css b/frontend/src/app/_elements/gradient-background/gradient-background.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.css +++ b/frontend/src/app/_elements/gradient-background/gradient-background.component.css diff --git a/frontend/src/app/_elements/gradient-background/gradient-background.component.html b/frontend/src/app/_elements/gradient-background/gradient-background.component.html new file mode 100644 index 00000000..3f30c35e --- /dev/null +++ b/frontend/src/app/_elements/gradient-background/gradient-background.component.html @@ -0,0 +1 @@ +<div #holder style="position: fixed; z-index: -1;" [ngStyle]="{'background': color}"></div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.spec.ts b/frontend/src/app/_elements/gradient-background/gradient-background.component.spec.ts index a9ea25b4..969c73b7 100644 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.spec.ts +++ b/frontend/src/app/_elements/gradient-background/gradient-background.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AddNewDatasetComponent } from './add-new-dataset.component'; +import { GradientBackgroundComponent } from './gradient-background.component'; -describe('AddNewDatasetComponent', () => { - let component: AddNewDatasetComponent; - let fixture: ComponentFixture<AddNewDatasetComponent>; +describe('GradientBackgroundComponent', () => { + let component: GradientBackgroundComponent; + let fixture: ComponentFixture<GradientBackgroundComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ AddNewDatasetComponent ] + declarations: [ GradientBackgroundComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(AddNewDatasetComponent); + fixture = TestBed.createComponent(GradientBackgroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_elements/gradient-background/gradient-background.component.ts b/frontend/src/app/_elements/gradient-background/gradient-background.component.ts new file mode 100644 index 00000000..1414bc60 --- /dev/null +++ b/frontend/src/app/_elements/gradient-background/gradient-background.component.ts @@ -0,0 +1,47 @@ +import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; + +@Component({ + selector: 'app-gradient-background', + templateUrl: './gradient-background.component.html', + styleUrls: ['./gradient-background.component.css'] +}) +export class GradientBackgroundComponent implements AfterViewInit { + + @ViewChild('holder') holderRef!: ElementRef; + private holder!: HTMLDivElement; + + + @Input() colorHorizontal1 = 'rgba(0, 8, 45, 0.5)'; + @Input() colorHorizontal2 = 'rgba(0, 52, 89, 0.5)'; + + @Input() colorVertical1 = 'rgba(0, 52, 89, 0.5)'; + @Input() colorVertical2 = 'rgba(0, 152, 189, 0.5)'; + + constructor() { } + + color: string = this.gradientHorizontal(); + + private width = 0; + private height = 0; + + gradientHorizontal(): string { + return `linear-gradient(90deg, ${this.colorHorizontal1} 0%, ${this.colorHorizontal2} 50%, ${this.colorHorizontal1} 100%), linear-gradient(0deg, ${this.colorVertical1} 0%, ${this.colorVertical2} 100%)`; + } + + resize() { + this.width = window.innerWidth; + this.height = window.innerHeight; + + this.holder.style.width = this.width + 'px'; + this.holder.style.height = this.height + 'px'; + } + + ngAfterViewInit(): void { + this.holder = <HTMLDivElement>this.holderRef.nativeElement; + + window.addEventListener('resize', () => this.resize()); + this.resize(); + + } + +} diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css index e69de29b..456a8df1 100644 --- a/frontend/src/app/_elements/graph/graph.component.css +++ b/frontend/src/app/_elements/graph/graph.component.css @@ -0,0 +1,20 @@ +.node-text { + position: absolute; + width: 100px; + height: 40px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + /*border: 1px solid red;*/ + transform: translate(-50%, -50%); +} + +.node-text:not(.inputs) { + color: transparent; +} + +.node-text:not(.inputs):hover { + color: var(--offwhite); +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html index 1c21fb6c..35753d40 100644 --- a/frontend/src/app/_elements/graph/graph.component.html +++ b/frontend/src/app/_elements/graph/graph.component.html @@ -1,3 +1,8 @@ -<div #graphWrapper class="w-100" style="height: 16rem;"> - <canvas #graphCanvas class="border"></canvas> +<div #graphWrapper class="w-100 position-relative" style="height: 14rem;"> + <!-- <ng-container *ngFor="let layer of layers; let i = index"> + <div [ngClass]="{'inputs': i==0}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 99.4" [style.top.%]="node.y * 100"> + {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i + < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? 'out' : '')) }} </div> + </ng-container> --> + <canvas #graphCanvas></canvas> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.ts b/frontend/src/app/_elements/graph/graph.component.ts index 8051acc3..da2c7767 100644 --- a/frontend/src/app/_elements/graph/graph.component.ts +++ b/frontend/src/app/_elements/graph/graph.component.ts @@ -1,6 +1,7 @@ import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; -import Dataset from 'src/app/_data/Dataset'; -import Model from 'src/app/_data/Model'; +import { RgbColor } from '@syncfusion/ej2-angular-heatmap'; +import Dataset, { ColumnInfo } from 'src/app/_data/Dataset'; +import Model, { Layer } from 'src/app/_data/Model'; @Component({ selector: 'app-graph', @@ -15,17 +16,19 @@ export class GraphComponent implements AfterViewInit { canvas!: ElementRef; @Input() model?: Model; - @Input() inputCols: number = 1; + //@Input() inputCols: number = 1; @Input() lineThickness: number = 2; @Input() nodeRadius: number = 15; - @Input() lineColor: string = '#00a8e8'; + @Input() lineColor1: RgbColor = new RgbColor(0, 168, 232); + @Input() lineColor2: RgbColor = new RgbColor(0, 70, 151); @Input() nodeColor: string = '#222277'; @Input() borderColor: string = '#00a8e8'; - @Input() inputNodeColor: string = '#ffdd11'; - @Input() outputNodeColor: string = '#44ee22'; + @Input() inputNodeColor: string = '#00a8e8'; + @Input() outputNodeColor: string = '#dfd7d7'; - private ctx?: CanvasRenderingContext2D; + private ctx!: CanvasRenderingContext2D; + @Input() inputColumns?: string[]; constructor() { } @@ -40,18 +43,19 @@ export class GraphComponent implements AfterViewInit { window.addEventListener('resize', () => { this.resize() }); this.update(); this.resize(); + //console.log(this.layers); } - layers?: Node[][]; + layers: Node[][] = []; update() { - this.layers = []; + this.layers.length = 0; let inputNodeIndex = 0; const inputLayer: Node[] = []; - while (inputNodeIndex < this.inputCols) { + while (this.inputColumns && inputNodeIndex < this.inputColumns.length) { const x = 0.5 / (this.model!.hiddenLayers + 2); - const y = (inputNodeIndex + 0.5) / this.inputCols; + const y = (inputNodeIndex + 0.5) / this.inputColumns.length; const node = new Node(x, y, this.inputNodeColor); inputLayer.push(node); inputNodeIndex += 1; @@ -62,9 +66,9 @@ export class GraphComponent implements AfterViewInit { while (layerIndex < this.model!.hiddenLayers + 1) { const newLayer: Node[] = []; let nodeIndex = 0; - while (nodeIndex < this.model!.hiddenLayerNeurons) { + while (nodeIndex < this.model!.layers[layerIndex - 1].neurons) { const x = (layerIndex + 0.5) / (this.model!.hiddenLayers + 2); - const y = (nodeIndex + 0.5) / this.model!.hiddenLayerNeurons; + const y = (nodeIndex + 0.5) / this.model!.layers[layerIndex - 1].neurons; const node = new Node(x, y, this.nodeColor); newLayer.push(node); nodeIndex += 1; @@ -80,7 +84,7 @@ export class GraphComponent implements AfterViewInit { } draw() { - this.ctx!.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); + this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); let index = 0; while (index < this.layers!.length - 1) { @@ -93,29 +97,38 @@ export class GraphComponent implements AfterViewInit { } for (let layer of this.layers!) { - for (let node of layer) { - this.drawNode(node); - } + layer.forEach((node, index) => { + this.drawNode(node, 0.5 / layer.length + 0.5); + }); } } + bezierOffset = 5; + drawLine(node1: Node, node2: Node) { - this.ctx!.strokeStyle = this.lineColor; - this.ctx!.lineWidth = this.lineThickness; - this.ctx!.beginPath(); - this.ctx!.moveTo(node1.x * this.width, node1.y * this.height); - this.ctx!.lineTo(node2.x * this.width, node2.y * this.height); - this.ctx!.stroke(); + const lineColor: RgbColor = this.lerpColor(this.lineColor1, this.lineColor2, node1.y); + this.ctx.strokeStyle = `rgb(${lineColor.R}, ${lineColor.G}, ${lineColor.B})`; + this.ctx.lineWidth = this.lineThickness; + this.ctx.beginPath(); + this.ctx.moveTo(node1.x * this.width, node1.y * this.height); + //this.ctx.lineTo(node2.x * this.width, node2.y * this.height); + const middle = (node1.x + (node2.x - node1.x) / 2) * this.width; + this.ctx.bezierCurveTo( + middle, node1.y * this.height, + middle, node2.y * this.height, + node2.x * this.width, node2.y * this.height); + this.ctx.stroke(); } - drawNode(node: Node) { - this.ctx!.fillStyle = node.color; - this.ctx!.strokeStyle = this.borderColor; - this.ctx!.lineWidth = this.lineThickness; - this.ctx!.beginPath(); - this.ctx!.arc(node.x * this.width, node.y * this.height, this.nodeRadius, 0, 2 * Math.PI); - this.ctx!.fill(); - this.ctx!.stroke(); + drawNode(node: Node, sizeMult: number) { + const lineColor: RgbColor = this.lerpColor(this.lineColor1, this.lineColor2, node.y); + this.ctx.strokeStyle = `rgb(${lineColor.R}, ${lineColor.G}, ${lineColor.B})`; + this.ctx.fillStyle = node.color; + this.ctx.lineWidth = this.lineThickness; + this.ctx.beginPath(); + this.ctx.arc(node.x * this.width, node.y * this.height, this.nodeRadius * sizeMult, 0, 2 * Math.PI); + this.ctx.fill(); + this.ctx.stroke(); } width = 200; @@ -134,6 +147,16 @@ export class GraphComponent implements AfterViewInit { this.draw(); } + + lerpColor(value1: RgbColor, value2: RgbColor, amount: number): RgbColor { + const newColor = new RgbColor(0, 0, 0); + amount = amount < 0 ? 0 : amount; + amount = amount > 1 ? 1 : amount; + newColor.R = value1.R + (value2.R - value1.R) * amount; + newColor.G = value1.G + (value2.G - value1.G) * amount; + newColor.B = value1.B + (value2.B - value1.B) * amount; + return newColor; + }; } class Node { diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.css b/frontend/src/app/_elements/item-dataset/item-dataset.component.css deleted file mode 100644 index dc851671..00000000 --- a/frontend/src/app/_elements/item-dataset/item-dataset.component.css +++ /dev/null @@ -1,23 +0,0 @@ -.card{ - margin-top:0; - padding: 0; -} -.p-2{ - margin: 0; - padding: 0; -} -hr{ - margin: 0; - padding: 0; -} -b{ - margin-left: 5px; - margin-right: 10px; -} -th{ - margin: 10px; - padding: 10px; -} -p{ - text-align: justify; -}
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.html b/frontend/src/app/_elements/item-dataset/item-dataset.component.html deleted file mode 100644 index 11ff61c3..00000000 --- a/frontend/src/app/_elements/item-dataset/item-dataset.component.html +++ /dev/null @@ -1,41 +0,0 @@ -<div class="card" style="min-width: 12rem;"> -<div class="card-header d-flex mb-2 justify-content-" style="padding: 0;margin: 0;"> - - <div class=" p-2 float-left "><b style="color: gray;">Naziv</b></div> - <div class=" p-2 float-left"><b>{{dataset.name}}</b></div> -</div> -<div class="card-body overflow-hidden"> - <b style="color: gray;">Opis</b> - <hr style="width: 20%;"> <p> {{dataset.description}}</p> - <hr> - <div class="d-flex justify-content-center"> - <div class=" p-2" > - <h4><span class="badge bg-secondary">{{dataset.extension}}</span></h4> - </div> - <div class="p-2"> - <span class="material-icons">{{visibleicon}}</span> - </div> - <div class="p-2"> - <span class="material-icons">{{accessibleicon}}</span> - </div> - </div> - <hr> - <div class="col text-center"> -<button (click)=toggleDisplayDiv() class="btn btn-primary btn-sm active " mat-raised-button color="primary" style="margin: 0.5rem;">Kolone</button> - <div [hidden]="isShowDiv" style="overflow: scroll; overflow-y: hidden;"> - <table class="table table-bordered table-md" > - <thead> - <th scope="col" *ngFor="let column of dataset.columnInfo" >{{column.columnName}}</th> - </thead> - </table> - </div> -</div> - <table> - <tr><td><span class="material-icons">calendar_today</span></td><td><span style="color: grey;"> <b> Kreirano</b></span></td><td>{{dataset.dateCreated |date}}</td></tr> - <tr><td><span class="material-icons">edit_calendar</span></td><td><span style="color: grey;"> <b> Poslednja izmena</b></span></td><td>{{dataset.lastUpdated |date}}</td></tr> - </table> - -</div> -<div class="card-footer"> - - </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.ts b/frontend/src/app/_elements/item-dataset/item-dataset.component.ts deleted file mode 100644 index 44b95310..00000000 --- a/frontend/src/app/_elements/item-dataset/item-dataset.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import Dataset from 'src/app/_data/Dataset'; - -@Component({ - selector: 'app-item-dataset', - templateUrl: './item-dataset.component.html', - styleUrls: ['./item-dataset.component.css'] -}) -export class ItemDatasetComponent { - - @Input() dataset: Dataset = new Dataset(); - visibleicon=''; - accessibleicon=''; - isShowDiv = true; - toggleDisplayDiv() { - this.isShowDiv = !this.isShowDiv; - } - constructor() { - } - ngOnInit(): void { - if(this.dataset.isPublic==true) - { - this.visibleicon='visibility' - } - else - { - this.visibleicon='visibility_off'; - } - - if(this.dataset.accessibleByLink==true) - { - this.accessibleicon='link' - } - else - { - this.accessibleicon='link_off'; - } - } -} - diff --git a/frontend/src/app/_elements/item-experiment/item-experiment.component.html b/frontend/src/app/_elements/item-experiment/item-experiment.component.html deleted file mode 100644 index 51fbfef3..00000000 --- a/frontend/src/app/_elements/item-experiment/item-experiment.component.html +++ /dev/null @@ -1,10 +0,0 @@ -<div class="card" style="min-width: 12rem;"> - <div class="card-header"> - Naziv eksperimenta: <b>{{experiment.name}}</b> - </div> - <div class="card-body overflow-hidden"> - <p class="card-text"> - Opis: {{experiment.description}} - </p> - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-experiment/item-experiment.component.ts b/frontend/src/app/_elements/item-experiment/item-experiment.component.ts deleted file mode 100644 index 31900d35..00000000 --- a/frontend/src/app/_elements/item-experiment/item-experiment.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import Experiment from 'src/app/_data/Experiment'; - -@Component({ - selector: 'app-item-experiment', - templateUrl: './item-experiment.component.html', - styleUrls: ['./item-experiment.component.css'] -}) -export class ItemExperimentComponent{ - - @Input() experiment: Experiment = new Experiment(); - - constructor() { } - -} diff --git a/frontend/src/app/_elements/item-model/item-model.component.css b/frontend/src/app/_elements/item-model/item-model.component.css deleted file mode 100644 index 5ea24c72..00000000 --- a/frontend/src/app/_elements/item-model/item-model.component.css +++ /dev/null @@ -1,23 +0,0 @@ -.card{ - margin: 0.5rem; - padding: 0; -} -.p-2{ - margin: 0; - padding: 0; -} -hr{ - margin: 0; - padding: 0; -} -b{ - margin-left: 5px; - margin-right: 10px; -} -th{ - margin: 10px; - padding: 10px; -} -p{ - text-align: justify; -}
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-model/item-model.component.html b/frontend/src/app/_elements/item-model/item-model.component.html deleted file mode 100644 index 447f023e..00000000 --- a/frontend/src/app/_elements/item-model/item-model.component.html +++ /dev/null @@ -1,58 +0,0 @@ -<div class="card" style="min-width: 12rem;"> - <div class="card-header d-flex mb-2 justify-content-" style="padding: 0;margin: 0;"> - - <div class=" p-2 float-left "><b style="color: gray;">Naziv</b></div> - <div class=" p-2 float-left"><b>{{model.name}}</b></div> - </div> - <div class="card-body overflow-hidden"> - <app-graph [model]="model"></app-graph> - <br> - <b style="color: gray;">Opis</b><hr style="width: 20%;"> - <p class="card-text"> - {{model.description}} - </p> - <hr> - - <div> - <table> - <tr><td><span class="material-icons">calendar_today</span></td><td><span style="color: grey;"> <b> Kreirano</b></span></td><td>{{model.dateCreated |date}}</td></tr> - <tr><td><span class="material-icons">edit_calendar</span></td><td><span style="color: grey;"> <b> Poslednja izmena</b></span></td><td>{{model.lastUpdated |date}}</td></tr> - </table> - </div> - - </div> - <button (click)=toggleDisplayDiv() class="btn btn-default btn-lg " mat-raised-button color="primary" style="margin: 0.5rem;">Parametri</button> - <div [hidden]="isShowDiv"> - <!-- <table> - <tr> - <td><span style="color: grey;"> <b> Nasumično raspoređivanje podataka</b></span></td><td>{{randomOrd}}</td> - </tr> - <tr> - <td><span style="color: grey;"> <b> Podela podataka na trening i test skup</b></span></td><td>{{randomOrd}}</td> - </tr> - <tr> - <td><span style="color: grey;"> <b> Veličina skupa za treniranje</b></span></td><td>{{randomOrd}}</td> - </tr> - </table>--> - <hr> - <table> - <tr> - <td><span style="color: grey;"> <b> Tip problema</b></span></td><td>{{model.type}}</td> - </tr> - <tr> - <td><span style="color: grey;"> <b> Optimizator</b></span></td><td>{{model.optimizer}}</td> - </tr> - <tr> - <td> <span style="color: grey;"> <b> Funkcija gubitka</b></span></td><td>{{model.lossFunction}}</td> - </tr> - <tr> - <td><span style="color: grey;"> <b> Batch size</b></span></td><td>{{model.batchSize}}</td> - </tr> - <tr> - <td><span style="color: grey;"> <b> Broj epoha</b></span></td><td>{{model.epochs}}</td> - </tr> - - </table> - - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-model/item-model.component.ts b/frontend/src/app/_elements/item-model/item-model.component.ts deleted file mode 100644 index b837667b..00000000 --- a/frontend/src/app/_elements/item-model/item-model.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import Model from 'src/app/_data/Model'; - -@Component({ - selector: 'app-item-model', - templateUrl: './item-model.component.html', - styleUrls: ['./item-model.component.css'] -}) -export class ItemModelComponent implements OnInit { - - @Input() model: Model = new Model(); - isShowDiv = true; - randomOrd=''; - - toggleDisplayDiv() { - this.isShowDiv = !this.isShowDiv; - } - - constructor() { } - - ngOnInit(): void { - /*if(this.model.randomOrder) - { - this.randomOrd='Da'; - } - else - { - this.randomOrd='Ne'; - } -*/ - } - -} diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.html b/frontend/src/app/_elements/item-predictor/item-predictor.component.html deleted file mode 100644 index 3199dcc8..00000000 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.html +++ /dev/null @@ -1,35 +0,0 @@ -<div class="card" style="min-width: 12rem;"> - <div class="card-header d-flex mb-2 justify-content-" style="padding: 0;margin: 0;"> - - <div class=" p-2 float-left "><b style="color: gray;">Prediktor</b></div> - - </div> - <div class="card-body overflow-hidden"> - <b style="color: gray;">Opis</b><hr style="width: 20%;"> - <p class="card-text"> - {{predictor.description}} - </p> - - <b style="color: gray;">Ulazne kolone</b> - <div style="overflow: scroll; overflow-y: hidden;"> - - <table class="table table-bordered table-md" > - <thead> - <th scope="col" *ngFor="let column of predictor.inputs" >{{column}}</th> - </thead> - </table> - </div> - <b style="color: gray;">Izlazna kolona: </b><b>{{predictor.output}}</b> - <hr> - <div> - <table> - <tr><td><span class="material-icons">calendar_today</span></td><td><span style="color: grey;"> <b> Kreirano</b></span></td><td>{{predictor.dateCreated |date}}</td></tr> - </table> - </div> - </div> - <div class="card-footer text-center"> - <button class="btn btn-md col-4" style="background-color:#003459; color:white;" - (click)="openPredictor();">Iskoristi</button> - - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.ts b/frontend/src/app/_elements/item-predictor/item-predictor.component.ts deleted file mode 100644 index 246032e0..00000000 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import Predictor from 'src/app/_data/Predictor'; - -@Component({ - selector: 'app-item-predictor', - templateUrl: './item-predictor.component.html', - styleUrls: ['./item-predictor.component.css'] -}) -export class ItemPredictorComponent implements OnInit { - - @Input() predictor: Predictor = new Predictor(); - - constructor(private router: Router) { } - - ngOnInit(): void { - } - - openPredictor() { - this.router.navigate(['predict/'+ this.predictor._id]); - } - -} diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.html b/frontend/src/app/_elements/line-chart/line-chart.component.html deleted file mode 100644 index c8f406f4..00000000 --- a/frontend/src/app/_elements/line-chart/line-chart.component.html +++ /dev/null @@ -1,5 +0,0 @@ -<div class="chart-wrapper"> - <canvas id="myChart"> - - </canvas> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.css b/frontend/src/app/_elements/metric-view/metric-view.component.css index e69de29b..f91c1ccc 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.css +++ b/frontend/src/app/_elements/metric-view/metric-view.component.css @@ -0,0 +1,10 @@ +#container{ + width: 100%; + height: 90%; + border-radius: 5px; + background-color:var(--ns-primary-25); + border:1px solid var(--ns-accent); +} +#line{ + background-color:#dfd7d7f0 ; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.html b/frontend/src/app/_elements/metric-view/metric-view.component.html index e7a4c547..d72bc92b 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.html +++ b/frontend/src/app/_elements/metric-view/metric-view.component.html @@ -1,5 +1,8 @@ -<div> - <app-line-chart> - - </app-line-chart> +<div id="container" class="d-flex justify-content-center flex-row w-100"> + <div id="line" style="width: 100%;height: 100%;background-color:var(--ns-bg-dark-100);"> + <app-line-chart></app-line-chart> + </div> + <div style="background-color: var(--ns-bg-dark-100);width: 50%;height: 50%;"> + <app-scatterchart></app-scatterchart> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.ts b/frontend/src/app/_elements/metric-view/metric-view.component.ts index 9193a0e5..6fd2f320 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.ts +++ b/frontend/src/app/_elements/metric-view/metric-view.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { SignalRService } from 'src/app/_services/signal-r.service'; -import { LineChartComponent } from '../line-chart/line-chart.component'; +import { LineChartComponent } from '../_charts/line-chart/line-chart.component'; + @Component({ selector: 'app-metric-view', templateUrl: './metric-view.component.html', @@ -9,13 +9,13 @@ import { LineChartComponent } from '../line-chart/line-chart.component'; export class MetricViewComponent implements OnInit { @ViewChild(LineChartComponent) linechartComponent!: LineChartComponent; - @Input() history!: any[]; - - constructor(private signalRService: SignalRService) { } + constructor() { } ngOnInit(): void { } + history: any[] = []; + update(history: any[]) { const myAcc: number[] = []; const myMae: number[] = []; @@ -28,7 +28,7 @@ export class MetricViewComponent implements OnInit { myEpochs.push(epoch + 1); for (let key in metrics) { let value = metrics[key]; - console.log(key, ':::', value, epoch); + //console.log(key, ':::', value, epoch); if (key === 'accuracy') { myAcc.push(parseFloat(value)); } diff --git a/frontend/src/app/_elements/model-load/model-load.component.css b/frontend/src/app/_elements/model-load/model-load.component.css deleted file mode 100644 index c716f964..00000000 --- a/frontend/src/app/_elements/model-load/model-load.component.css +++ /dev/null @@ -1,17 +0,0 @@ -.btnType1 { - background-color: #003459; - color: white; - padding-top: 2vh; - padding-bottom: 2vh; -} -.btnType2 { - background-color: white; - color: #003459; - border-color: #003459; - padding-top: 2vh; - padding-bottom: 2vh; -} -.selectedModelClass { - /*border-color: 2px solid #003459;*/ - background-color: lightblue; -}
\ No newline at end of file diff --git a/frontend/src/app/_elements/model-load/model-load.component.html b/frontend/src/app/_elements/model-load/model-load.component.html index 1f9852d1..dcb35c21 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.html +++ b/frontend/src/app/_elements/model-load/model-load.component.html @@ -1,10 +1,12 @@ <div> <div class="d-flex flex-row justify-content-center align-items-center mt-3 mb-5"> - <button type="button" id="btnMyModel" class="btn" (click)="viewMyModelsForm()" [ngClass]="{'btnType1': showMyModels, 'btnType2': !showMyModels}"> + <button type="button" id="btnMyModel" class="btn" (click)="viewMyModelsForm()" + [ngClass]="{'btnType1': showMyModels, 'btnType2': !showMyModels}"> Izaberite model iz kolekcije </button> <h3 class="mt-3 mx-3">ili</h3> - <button type="button" id="btnNewModel" class="btn" (click)="viewNewModelForm()" [ngClass]="{'btnType1': !showMyModels, 'btnType2': showMyModels}"> + <button type="button" id="btnNewModel" class="btn" (click)="viewNewModelForm()" + [ngClass]="{'btnType1': !showMyModels, 'btnType2': showMyModels}"> Dodajte novi model </button> </div> @@ -15,7 +17,8 @@ <div *ngIf="showMyModels" class="px-5"> <div class="overflow-auto" style="max-height: 500px;"> <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let model of myModels|filter:((forExperiment != undefined) ? forExperiment.type : '')" [ngClass]="{'selectedModelClass': this.selectedModel == model}"> + <li class="list-group-item p-3" *ngFor="let model of myModels|filter:term|filter:(forExperiment ? forExperiment.type : '')" + [ngClass]="{'selectedModelClass': this.selectedModel == model}"> <app-item-model name="usersModel" [model]="model" (click)="selectThisModel(model);"> </app-item-model> </li> @@ -40,7 +43,7 @@ <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> </div> </div> - + </div> <h2 class="mt-5 mb-4 mx-5">Parametri treniranja modela:</h2> <div> @@ -51,7 +54,8 @@ <label for="type" class="col-form-label">Tip problema: </label> </div> <div class="col-2"> - <select id="typeOptions" class="form-select" name="type" [(ngModel)]="newModel.type" (change)="filterOptions()"> + <select id=typeOptions class="form-select" name="type" [(ngModel)]="newModel.type" + (change)="filterOptions()"> <option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> @@ -64,7 +68,10 @@ <label for="hiddenLayers" class="col-form-label">Broj skrivenih slojeva: </label> </div> <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()"> + <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> @@ -75,7 +82,7 @@ <label for="optimizer" class="col-form-label">Optimizacija: </label> </div> <div class="col-2"> - <select id="optimizerOptions" class="form-select" name="optimizer" [(ngModel)]="newModel.optimizer"> + <select id=optimizerOptions class="form-select" name="optimizer" [(ngModel)]="newModel.optimizer"> <option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> @@ -89,7 +96,8 @@ <label for="hiddenLayerNeurons" class="col-form-label">Broj neurona skrivenih slojeva: </label> </div> <div class="col-1"> - <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> + <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" + [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> </div> </div> @@ -99,7 +107,8 @@ <label for="lossFunction" class="col-form-label">Funkcija troška: </label> </div> <div class="col-2"> - <select id="lossFunctionOptions" class="form-select" name="lossFunction" [(ngModel)]="newModel.lossFunction" aria-checked="true"> + <select id=lossFunctionOptions class="form-select" name="lossFunction" + [(ngModel)]="newModel.lossFunction" aria-checked="true"> <option *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" [value]="option"> @@ -109,21 +118,23 @@ </div> <div class="col-1"></div> <div class="col-3"> - <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: <b>{{newModel.batchSize}}</b><br>(izaberite stepen dvojke)</label> + <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: </label> </div> <div class="col-1"> - - <input type="number" min="0" step="1" max="7" class="form-control" name="batchSizePower" [(ngModel)]="batchSizePower" (click)="updateBatchSize()"> - + + <input type="number" min="0" step="1" max="7" class="form-control" name="batchSizePower" [(ngModel)]="batchSizePower" (click)="updateBatchSize()" > + {{newModel.batchSize}} + </div> - + <div class="row p-2"> <div class="col-1"></div> <div class="col-3 m-1"> <label for="epochs" class="col-form-label">Broj epoha: </label> </div> <div class="col-1"> - <input type="number" min="1" max="1000" class="form-control" name="epochs" [(ngModel)]="newModel.epochs"> + <input type="number" min="1" max="1000" class="form-control" name="epochs" + [(ngModel)]="newModel.epochs"> </div> </div> </div> @@ -137,7 +148,8 @@ <div class="row p-2" style="align-self: center;"> <div class="col-1"></div> <div class="col-3"> - <label for="hiddenLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija aktivacije<br>skrivenih slojeva:</label> + <label for="hiddenLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije<br>skrivenih slojeva:</label> </div> <div class="col-2 mt-2"> <div *ngFor="let item of [].constructor(newModel.hiddenLayers); let i = index"> @@ -145,7 +157,8 @@ <div class="input-group-prepend"> <span class="input-group-text">#{{i+1}}</span> </div> - <select [id]="'hiddenLayerActivationFunctionOption_'+i" class="form-select" [(ngModel)]="newModel.hiddenLayerActivationFunctions[i]"> + <select [id]="'hiddenLayerActivationFunctionOption_'+i" class="form-select" + [(ngModel)]="newModel.hiddenLayerActivationFunctions[i]" > <option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> @@ -157,10 +170,12 @@ </div> <div class="col-1"></div> <div class="col-2"> - <label for="outputLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija aktivacije<br>izlaznog sloja:</label> + <label for="outputLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije<br>izlaznog sloja:</label> </div> <div class="col-2 mt-2"> - <select id="outputLayerActivationFunctionOptions" class="form-select" name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <select id=outputLayerActivationFunctionOptions class="form-select" + name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> <option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> @@ -173,23 +188,26 @@ </div> </div> - <!--<div class="form-check form-check-inline overflow-auto m-4" style="width: max-content;"> + <div class="form-check form-check-inline overflow-auto m-4" style="width: max-content;"> <h3>Izaberite metrike:</h3> <div id="divMetricsinput" class="mt-2 mx-5"> - <div *ngFor="let option of Object.keys(metrics); let optionName of Object.values(metrics) " class="form-check form-check-inline"> + <div *ngFor="let option of Object.keys(metrics); let optionName of Object.values(metrics) " + class="form-check form-check-inline"> - <input name="cbmetrics" class="form-check-input" type="checkbox" value="{{option}}" id="metrics_{{option}}" style="float: left;" checked> + <input name="cbmetrics" class="form-check-input" type="checkbox" value="{{option}}" + id="metrics_{{option}}" style="float: left;" checked> <label class="form-check-label" for="metrics_{{option}}" for="inlineCheckbox2"> {{optionName}} </label> </div> </div> - </div>--> - + </div> + <div class="form-group row mt-3 mb-3"> <div class="col"></div> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="uploadModel();">Sačuvaj + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" + (click)="uploadModel();">Sačuvaj model</button> <div class="col"></div> </div> diff --git a/frontend/src/app/_elements/model-load/model-load.component.spec.ts b/frontend/src/app/_elements/model-load/model-load.component.spec.ts deleted file mode 100644 index 1dafd966..00000000 --- a/frontend/src/app/_elements/model-load/model-load.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ModelLoadComponent } from './model-load.component'; - -describe('ModelLoadComponent', () => { - let component: ModelLoadComponent; - let fixture: ComponentFixture<ModelLoadComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ ModelLoadComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(ModelLoadComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_elements/model-load/model-load.component.ts b/frontend/src/app/_elements/model-load/model-load.component.ts index fb4b3fd0..dbca3d17 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.ts +++ b/frontend/src/app/_elements/model-load/model-load.component.ts @@ -2,9 +2,7 @@ import { Component, OnInit, ViewChild, Output, EventEmitter, Input } from '@angu 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 { AuthService } from 'src/app/_services/auth.service'; import { ModelsService } from 'src/app/_services/models.service'; -import { SignalRService } from 'src/app/_services/signal-r.service'; import { GraphComponent } from '../graph/graph.component'; @@ -16,7 +14,7 @@ import { GraphComponent } from '../graph/graph.component'; export class ModelLoadComponent implements OnInit { @ViewChild(GraphComponent) graph!: GraphComponent; - @Input() forExperiment?: Experiment; + @Input() forExperiment?:Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); newModel: Model = new Model(); @@ -33,42 +31,24 @@ export class ModelLoadComponent implements OnInit { shared = Shared; term: string = ""; + selectedProblemType: string = ''; selectedMetrics = []; lossFunction: any = LossFunction; showMyModels: boolean = true; - batchSizePower: number = 2; - - constructor(private modelsService: ModelsService, private authService: AuthService) { - //console.log("forExperiment = ", this.forExperiment); - this.fetchModels(); - - this.authService.loggedInEvent.subscribe(_ => { - this.fetchModels(); - }) - } - - fetchModels(andSelectWithId: string | null = '') { - //if (this.forExperiment == undefined) { + constructor(private modelsService: ModelsService) { this.modelsService.getMyModels().subscribe((models) => { - this.myModels = models.reverse(); - this.selectThisModel(this.myModels.filter(x => x._id == andSelectWithId)[0]); + this.myModels = models; }); - /*} - else { - this.modelsService.getMyModelsByType(ProblemType.Regression).subscribe((models) => { - this.myModels = models; - //console.log("modeli po tipu: ", this.myModels); - }); - }*/ } ngOnInit(): void { } - - updateBatchSize() { - this.newModel.batchSize = 2 ** this.batchSizePower; + batchSizePower:number=1; + updateBatchSize() + { + this.newModel.batchSize=2**this.batchSizePower; } updateGraph() { @@ -92,14 +72,9 @@ export class ModelLoadComponent implements OnInit { this.newModel.uploaderId = Shared.userId; this.modelsService.addModel(this.newModel).subscribe((response) => { - console.log(this.newModel); - //Shared.openDialog('Model dodat', 'Model je uspešno dodat u bazu.'); - - Shared.openYesNoDialog("Model dodat", "Model je uspešno dodat u bazu. Da li želite da nastavite treniranje sa dodatim modelom?", () => { - this.fetchModels(response._id); - this.showMyModels = true; - }); - this.fetchModels(); + Shared.openDialog('Model dodat', 'Model je uspešno dodat u bazu.'); + // treba da se selektuje nov model u listi modela + //this.selectedModel = }, (error) => { Shared.openDialog('Greška', 'Model sa unetim nazivom već postoji u Vašoj kolekciji. Promenite naziv modela i nastavite sa kreiranim datasetom.'); }); diff --git a/frontend/src/app/_elements/navbar/navbar.component.css b/frontend/src/app/_elements/navbar/navbar.component.css index e69de29b..fcfad876 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.css +++ b/frontend/src/app/_elements/navbar/navbar.component.css @@ -0,0 +1,8 @@ +.dropdown-item:hover { + background-color: var(--ns-primary); +} + +h4 { + margin-top: 0.82rem; + margin-right: 10px; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/navbar/navbar.component.html b/frontend/src/app/_elements/navbar/navbar.component.html index 1988b834..105151aa 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.html +++ b/frontend/src/app/_elements/navbar/navbar.component.html @@ -1,17 +1,15 @@ -<header class="sticky-top p-3 bg-dark text-white"> +<header class="text-offwhite" style="background-color: #002b49;"> <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"> - <img src="../../../assets/svg/logo_no_text.svg" class="bi me-2" width="64" height="40"> + <img src="../../../assets/images/logo.png" class="bi me-2" width="64" height="64"> + <h4>Igr<span class="highlight">ann</span>onica</h4> </a> <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"> - <li><a routerLink="" class="nav-link px-2" [class]="(currentUrl === '') ? 'text-secondary' : 'text-white'">Početna</a></li> - <li><a routerLink="experiment" class="nav-link px-2" [class]="(currentUrl === '/experiment') ? 'text-secondary' : 'text-white'">Napravi eksperiment</a> + <li><a routerLink="experiment" class="nav-link px-2" [class]="(currentUrl === '/experiment') ? 'text-primary' : 'text-offwhite'">Napravi eksperiment</a> </li> - <li><a routerLink="training" class="nav-link px-2" [class]="(currentUrl === '/training') ? 'text-secondary' : 'text-white'">Treniraj model</a> - </li> - <li><a routerLink="my-predictors" class="nav-link px-2" [class]="(currentUrl === '/my-predictors') ? 'text-secondary' : 'text-white' + (shared.loggedIn) ? '' : 'disabled'">Predvidi</a> + <li><a routerLink="archive" class="nav-link px-2" [class]="(currentUrl === '/archive') ? 'text-primary' : 'text-offwhite'">Arhiva</a> </li> </ul> @@ -19,12 +17,8 @@ <a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false"> <img [src]="'/assets/profilePictures/'+ shared.photoId +'.png'" alt="mdo" width="32" height="32" class="rounded-circle"> </a> - <ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1" style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 34px);" data-popper-placement="bottom-end"> - <li><a class="dropdown-item" routerLink="my-datasets">Moji izvori podataka</a></li> - <li><a class="dropdown-item" routerLink="my-models">Moji modeli</a></li> - <li><a class="dropdown-item" routerLink="my-predictors">Moji prediktori</a></li> + <ul class="dropdown-menu text-small ns-bg-dark-100" aria-labelledby="dropdownUser1" style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 34px);" data-popper-placement="bottom-end"> <li><a class="dropdown-item" routerLink="profile">Moj profil</a></li> - <li><a class="dropdown-item" routerLink="settings" disabled>Podešavanja</a></li> <li> <hr class="dropdown-divider"> </li> @@ -32,10 +26,10 @@ </ul> </div> <div *ngIf="!shared.loggedIn" class="dropdown text-end"> - <button type="button" mat-raised-button color="primary" class="mx-2" data-bs-toggle="modal" data-bs-target="#modalForLogin"> + <button type="button" mat-raised-button color="accent" class="mx-2" data-bs-toggle="modal" data-bs-target="#modalForLogin"> Prijavi se </button> - <button type="button" mat-raised-button color="primary" data-bs-toggle="modal" data-bs-target="#modalForRegister"> + <button type="button" mat-raised-button color="accent" data-bs-toggle="modal" data-bs-target="#modalForRegister"> Registruj se </button> </div> diff --git a/frontend/src/app/_elements/navbar/navbar.component.ts b/frontend/src/app/_elements/navbar/navbar.component.ts index d5d1744f..e2551f7a 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.ts +++ b/frontend/src/app/_elements/navbar/navbar.component.ts @@ -1,30 +1,26 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Location } from '@angular/common'; import { AuthService } from '../../_services/auth.service'; import shared from 'src/app/Shared'; import { UserInfoService } from 'src/app/_services/user-info.service'; import { MatDialog } from '@angular/material/dialog'; -import { SignalRService } from 'src/app/_services/signal-r.service'; @Component({ selector: 'app-navbar', templateUrl: './navbar.component.html', - styleUrls: ['./navbar.component.css'] + styleUrls: ['./navbar.component.css'], + encapsulation: ViewEncapsulation.Emulated }) export class NavbarComponent implements OnInit { currentUrl: string; shared = shared; - constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService, private matDialog: MatDialog, private signalRService: SignalRService) { + constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService, private matDialog: MatDialog) { shared.dialog = matDialog; this.currentUrl = this.location.path(); this.location.onUrlChange(() => { this.currentUrl = this.location.path(); - }); - - this.auth.loggedInEvent.subscribe(_ => { - this.signalRService.startConnection(); }) } @@ -39,6 +35,5 @@ export class NavbarComponent implements OnInit { logOut() { this.auth.logOut(); - this.signalRService.stopConnection(); } } diff --git a/frontend/src/app/_elements/notifications/notifications.component.ts b/frontend/src/app/_elements/notifications/notifications.component.ts index f324662a..5716c1e6 100644 --- a/frontend/src/app/_elements/notifications/notifications.component.ts +++ b/frontend/src/app/_elements/notifications/notifications.component.ts @@ -25,6 +25,7 @@ export class NotificationsComponent implements OnInit { const existingNotification = this.notifications.find(x => x.id === mId) const progress = ((currentEpoch + 1) / totalEpochs); //console.log("Ukupno epoha", totalEpochs, "Trenutna epoha:", currentEpoch); + //console.log("stat:", stat); if (!existingNotification) this.notifications.push(new Notification(`Treniranje modela: ${mName}`, mId, progress, true)); else { diff --git a/frontend/src/app/_elements/playlist/playlist.component.css b/frontend/src/app/_elements/playlist/playlist.component.css new file mode 100644 index 00000000..353a094c --- /dev/null +++ b/frontend/src/app/_elements/playlist/playlist.component.css @@ -0,0 +1,61 @@ +.ns-wrapper { + width: 100%; + max-width: 800px; + max-height: 600px; + height: 100%; + transform-style: preserve-3d; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; +} + +.ns-cards { + position: relative; + width: 300%; + height: 25rem; + margin-bottom: 20px; +} + +.ns-card { + position: absolute; + width: 60%; + height: 100%; + left: 0; + right: 0; + margin: auto; + transition: transform 0.4s ease; + cursor: pointer; +} + +.ns-card:hover { + opacity: 1 !important; +} + +input[type=radio] { + display: none; +} + +#item-1:checked~.ns-cards #view-item-3, +#item-2:checked~.ns-cards #view-item-1, +#item-3:checked~.ns-cards #view-item-2 { + transform: translatex(-40%) scale(0.8); + opacity: 0.5; + z-index: 0; +} + +#item-1:checked~.ns-cards #view-item-2, +#item-2:checked~.ns-cards #view-item-3, +#item-3:checked~.ns-cards #view-item-1 { + transform: translatex(40%) scale(0.8); + opacity: 0.5; + z-index: 0; +} + +#item-1:checked~.ns-cards #view-item-1, +#item-2:checked~.ns-cards #view-item-2, +#item-3:checked~.ns-cards #view-item-3 { + transform: translatex(0) scale(1); + opacity: 1; + z-index: 1; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/playlist/playlist.component.html b/frontend/src/app/_elements/playlist/playlist.component.html new file mode 100644 index 00000000..b82de163 --- /dev/null +++ b/frontend/src/app/_elements/playlist/playlist.component.html @@ -0,0 +1,19 @@ +<div class="ns-wrapper" *ngIf="tableDatas && tableDatas.length==3"> + <input type="radio" name="slider" id="item-1" value="0" [(ngModel)]="selectedId"> + <input type="radio" name="slider" id="item-2" value="1" [(ngModel)]="selectedId"> + <input type="radio" name="slider" id="item-3" value="2" [(ngModel)]="selectedId"> + <div class="ns-cards"> + <label class="ns-card ns-bg-dark-100 ns-border-primary rounded" for="item-1" id="view-item-1"> + <app-datatable [tableData]="tableDatas[0]"></app-datatable> + </label> + <label class="ns-card ns-bg-dark-100 ns-border-primary rounded" for="item-2" id="view-item-2"> + <app-datatable [tableData]="tableDatas[1]"></app-datatable> + </label> + <label class="ns-card ns-bg-dark-100 ns-border-primary rounded" for="item-3" id="view-item-3"> + <app-datatable [tableData]="tableDatas[2]"></app-datatable> + </label> + </div> + <div class="ns-infobox text-offwhite"> + <h2>{{datasets[getIndex(selectedId)].name}}</h2> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/playlist/playlist.component.spec.ts b/frontend/src/app/_elements/playlist/playlist.component.spec.ts new file mode 100644 index 00000000..0afe8041 --- /dev/null +++ b/frontend/src/app/_elements/playlist/playlist.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlaylistComponent } from './playlist.component'; + +describe('PlaylistComponent', () => { + let component: PlaylistComponent; + let fixture: ComponentFixture<PlaylistComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PlaylistComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PlaylistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/playlist/playlist.component.ts b/frontend/src/app/_elements/playlist/playlist.component.ts new file mode 100644 index 00000000..7f476178 --- /dev/null +++ b/frontend/src/app/_elements/playlist/playlist.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import { TableData } from 'src/app/_elements/datatable/datatable.component'; +import { CsvParseService } from 'src/app/_services/csv-parse.service'; +import { DatasetsService } from 'src/app/_services/datasets.service'; + +@Component({ + selector: 'app-playlist', + templateUrl: './playlist.component.html', + styleUrls: ['./playlist.component.css'] +}) +export class PlaylistComponent implements OnInit { + + selectedId: string = "0"; + + @Input() datasets!: Dataset[]; + + tableDatas?: TableData[]; + + constructor(private datasetService: DatasetsService, private csv: CsvParseService) { + + } + + getIndex(str: string) { + return parseInt(str); + } + + ngOnInit(): void { + this.tableDatas = []; + + this.datasets.forEach((dataset, index) => { + if (index < 3) { + this.datasetService.getDatasetFile(dataset.fileId).subscribe((file: string | undefined) => { + if (file) { + const tableData = new TableData(); + tableData.hasInput = true; + tableData.loaded = true; + tableData.numRows = dataset.rowCount; + tableData.numCols = dataset.columnInfo.length; + tableData.data = this.csv.csvToArray(file, (dataset.delimiter == "razmak") ? " " : (dataset.delimiter.toString() == "") ? "," : dataset.delimiter); + this.tableDatas!.push(tableData); + } + }); + } + }); + + //console.log(this.tableDatas); + } +} diff --git a/frontend/src/app/_elements/reactive-background/reactive-background.component.html b/frontend/src/app/_elements/reactive-background/reactive-background.component.html index 756952fb..c63dd6ac 100644 --- a/frontend/src/app/_elements/reactive-background/reactive-background.component.html +++ b/frontend/src/app/_elements/reactive-background/reactive-background.component.html @@ -1 +1 @@ -<canvas id="bgCanvas" width="200" height="200" style="position: fixed; z-index: -1;"></canvas>
\ No newline at end of file +<canvas #bgCanvas width="200" height="200" style="position: fixed; z-index: -1;"></canvas>
\ No newline at end of file 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 980e3e6f..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,14 +1,19 @@ -import { Component, Input, OnInit } from '@angular/core'; +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', templateUrl: './reactive-background.component.html', styleUrls: ['./reactive-background.component.css'] }) -export class ReactiveBackgroundComponent implements OnInit { +export class ReactiveBackgroundComponent implements AfterViewInit { + + @ViewChild('bgCanvas') canvasRef!: ElementRef; @Input() numPoints: number = 450; @Input() speed: number = 0.001; // 0-1 + @Input() scrollSpeed: number = 1; @Input() maxSize: number = 6; @Input() minDistance: number = 0.07; //0-1 @@ -19,30 +24,43 @@ export class ReactiveBackgroundComponent implements OnInit { @Input() pointColor: string = '#ffffff'; @Input() cursorLineColor: string = '#ff0000'; + @Input() animate: boolean = true; + @Input() fill: number = 1.0; + + private fleeSpeed = 0.005; + private points: Point[] = []; private width = 200; private height = 200; private ratio = 1; - private canvas?: HTMLCanvasElement; - private ctx?: CanvasRenderingContext2D; + private canvas!: HTMLCanvasElement; + private ctx!: CanvasRenderingContext2D; private time: number = 0; - constructor() { } + constructor(private cookie: CookieService) { } private mouseX = 0; private mouseY = 0; - ngOnInit(): void { - + ngAfterViewInit(): void { document.addEventListener('mousemove', (e) => { this.mouseX = e.clientX / this.width; this.mouseY = e.clientY / this.height; }) - this.canvas = (<HTMLCanvasElement>document.getElementById('bgCanvas')); + document.addEventListener('mouseleave', _ => { + this.mouseX = -1; + this.mouseY = -1; + }) + + document.addEventListener('scroll', (e) => { + this.scrollBackground(e); + }) + + this.canvas = (<HTMLCanvasElement>this.canvasRef.nativeElement); const ctx = this.canvas.getContext('2d'); if (ctx) { this.ctx = ctx; @@ -64,41 +82,77 @@ export class ReactiveBackgroundComponent implements OnInit { this.resize(); setInterval(() => { + if (this.cookie.check('animateBackground')) { + this.animate = this.cookie.get('animateBackground') == 'true'; + } + if (this.cookie.check('backgroundFill')) { + this.fill = parseFloat(this.cookie.get('backgroundFill')); + } + 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 - (amount / this.height) * this.scrollSpeed; + this.keepPointWithinBounds(point); + }) } drawBackground() { if (!this.ctx || !this.canvas) return; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - this.ctx.fillStyle = this.bgColor; - this.ctx.fillRect(0, 0, this.width, this.height); + //this.ctx.fillStyle = this.bgColor; + //this.ctx.fillRect(0, 0, this.width, this.height); this.points.forEach((point, index) => { + if (index > this.numPoints * this.fill) return; + this.drawLines(point, index); this.drawPoint(point); - this.updatePoint(point); + + if (this.animate) + this.updatePoint(point); }); //this.drawPoint(new Point(this.mouseX, this.mouseY, 12, 0)); - - this.time += 1; } drawLines(p: Point, index: number) { let i = index + 1; - while (i < this.points.length) { + while (i < this.points.length * this.fill) { const otherPoint = this.points[i]; const dist = this.distance(p.x, p.y, otherPoint.x, otherPoint.y); if (dist < this.minDistance) { const h = HEX[Math.round((1 - dist / this.minDistance) * 16)] - this.ctx!.strokeStyle = this.lineColor + h + h; - this.ctx!.beginPath(); - this.ctx!.moveTo(p.x * this.width, p.y * this.height); - this.ctx!.lineTo(otherPoint.x * this.width, otherPoint.y * this.height); - this.ctx!.stroke(); + this.ctx.strokeStyle = this.lineColor + h; + this.ctx.lineWidth = this.maxSize / 2; + this.ctx.beginPath(); + this.ctx.moveTo(p.x * this.width, p.y * this.height); + this.ctx.lineTo(otherPoint.x * this.width, otherPoint.y * this.height); + this.ctx.stroke(); } i++; @@ -106,10 +160,10 @@ export class ReactiveBackgroundComponent implements OnInit { } drawPoint(p: Point) { - this.ctx!.fillStyle = this.pointColor; - this.ctx!.beginPath(); - this.ctx!.arc(p.x * this.width, p.y * this.height, p.size, 0, 2 * Math.PI); - this.ctx!.fill(); + this.ctx.fillStyle = this.pointColor; + this.ctx.beginPath(); + this.ctx.arc(p.x * this.width, p.y * this.height, p.size * this.screenDepth(p.x), 0, 2 * Math.PI); + this.ctx.fill(); } resize() { @@ -122,40 +176,66 @@ export class ReactiveBackgroundComponent implements OnInit { this.canvas.height = this.height; } + if (this.cookie.check('animateBackground')) { + this.animate = this.cookie.get('animateBackground') == 'true'; + } + if (this.cookie.check('backgroundFill')) { + this.fill = parseFloat(this.cookie.get('backgroundFill')); + } + this.drawBackground(); } updatePoint(p: Point) { + const mx = this.mouseX; + const my = this.mouseY; + const distToCursor = this.distance(p.x, p.y, mx, my); + + if (distToCursor < this.cursorDistance) { + + const t = (distToCursor / this.cursorDistance); + p.x -= ((mx - p.x) / distToCursor) * this.speed * (1 + t * 2); + p.y -= ((my - p.y) / distToCursor) * this.speed * (1 + t * 2); + + p.direction = this.lerp(p.direction, Math.atan2(my - p.y, mx - p.x) * 180 / Math.PI, t); + + const grd = this.ctx.createLinearGradient(p.x * this.width, p.y * this.height, mx * this.width, my * this.height); + const alpha = HEX[Math.round(p.size / this.maxSize * (HEX.length - 1))]; + grd.addColorStop(0, this.cursorLineColor + alpha); + grd.addColorStop(0.5, this.cursorLineColor + '00'); + this.ctx.strokeStyle = grd; + this.ctx.beginPath(); + this.ctx.moveTo(p.x * this.width, p.y * this.height); + this.ctx.lineTo(mx * this.width, my * this.height); + this.ctx.stroke(); + } + const vx = Math.sin(p.direction); const vy = Math.cos(p.direction); p.x = p.x + vx * this.speed; p.y = p.y + vy * this.speed; - const mx = this.mouseX; - const my = this.mouseY; - const distToCursor = this.distance(p.x, p.y, mx, my); - if (distToCursor < this.cursorDistance) { + this.keepPointWithinBounds(p); + } - p.x -= ((mx - p.x) / distToCursor) / 500; - p.y -= ((my - p.y) / distToCursor) / 500; - - const grd = this.ctx!.createLinearGradient(p.x * this.width, p.y * this.height, mx * this.width, my * this.height); - grd.addColorStop(0, this.cursorLineColor + 'ff'); - grd.addColorStop(1, this.cursorLineColor + '00'); - this.ctx!.strokeStyle = grd; - this.ctx!.beginPath(); - this.ctx!.moveTo(p.x * this.width, p.y * this.height); - this.ctx!.lineTo(mx * this.width, my * this.height); - this.ctx!.stroke(); - } + lerp(start: number, end: number, amt: number) { + return (1 - amt) * start + amt * end + } - p.x %= 1; - p.y %= 1; + keepPointWithinBounds(p: Point) { + p.x = p.x % 1.0; + p.y = p.y % 1.0; + p.x = ((1 - Math.sign(p.x)) / 2) + p.x; + p.y = ((1 - Math.sign(p.y)) / 2) + p.y; } distance(x1: number, y1: number, x2: number, y2: number): number { - return Math.sqrt(((x2 - x1) ** 2) + ((y2 / this.ratio - y1 / this.ratio) ** 2)); + return Math.sqrt(((x2 - x1) ** 2) + ((y2 / this.ratio - y1 / this.ratio) ** 2) / this.screenDepth(x1)) * this.ratio; + } + + screenDepth(x: number): number { + return (1.5 - Math.sin(x * Math.PI)); } } @@ -168,4 +248,4 @@ class Point { ) { } } -const HEX = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
\ No newline at end of file +const HEX = ['00', '11', '22', '33', '44', '55', '66', '77', '88', '99', 'aa', 'bb', 'cc', 'dd', 'ee', 'ff'];
\ No newline at end of file diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css index e69de29b..bdfd06c9 100644 --- a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css @@ -0,0 +1,3 @@ +mat-dialog-container{ + background: var(ns-bg-dark-50) !important; +}
\ No newline at end of file diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html index 82365193..2d7e4d86 100644 --- a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html @@ -1,7 +1,9 @@ -<h2 mat-dialog-title class="text-muted">{{data.title}}</h2> -<div mat-dialog-content class="mt-4" style="color: rgb(81, 76, 76);"> - {{data.message}} -</div> -<div mat-dialog-actions class="d-flex justify-content-center mt-4"> - <button mat-button cdkFocusInitial (click)="onOkClick()" style="background-color: lightgray;">OK</button> -</div>
\ No newline at end of file + + + <h2 mat-dialog-title >{{data.title}}</h2> + <div mat-dialog-content class="mt-4 text-offwhite" > + {{data.message}} + </div> + <div mat-dialog-actions class="d-flex justify-content-center mt-4"> + <button mat-raised-button cdkFocusInitial (click)="onOkClick()" color="basic">OK</button> + </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.css b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.css index e69de29b..e69de29b 100644 --- a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.css +++ b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.css diff --git a/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html new file mode 100644 index 00000000..7ba286cb --- /dev/null +++ b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html @@ -0,0 +1,16 @@ +<h1 mat-dialog-title>Enkodiranje svih kolona</h1> +<div mat-dialog-content> + <p>Odaberite tip enkodinga za sve kolone zajedno:</p> + <mat-form-field> + <mat-select matNativeControl [(value)]="selectedEncodingType"> + <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + <p>Da li ste sigurni u izbor?</p> +</div> +<div mat-dialog-actions> + <button mat-button [mat-dialog-close]="selectedEncodingType" cdkFocusInitial>Da</button> + <button mat-button (click)="onNoClick()">Odustani</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-experiment/item-experiment.component.spec.ts b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.spec.ts index 1da7d05d..77f30ae3 100644 --- a/frontend/src/app/_elements/item-experiment/item-experiment.component.spec.ts +++ b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ItemExperimentComponent } from './item-experiment.component'; +import { EncodingDialogComponent } from './encoding-dialog.component'; -describe('ItemExperimentComponent', () => { - let component: ItemExperimentComponent; - let fixture: ComponentFixture<ItemExperimentComponent>; +describe('EncodingDialogComponent', () => { + let component: EncodingDialogComponent; + let fixture: ComponentFixture<EncodingDialogComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ItemExperimentComponent ] + declarations: [ EncodingDialogComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ItemExperimentComponent); + fixture = TestBed.createComponent(EncodingDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.ts b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.ts new file mode 100644 index 00000000..3b7560bf --- /dev/null +++ b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { Encoding } from 'src/app/_data/Experiment'; + + +@Component({ + selector: 'app-encoding-dialog', + templateUrl: './encoding-dialog.component.html', + styleUrls: ['./encoding-dialog.component.css'] +}) +export class EncodingDialogComponent implements OnInit { + + selectedEncodingType?: Encoding; + Encoding = Encoding; + Object = Object; + + constructor(public dialogRef: MatDialogRef<EncodingDialogComponent>) + { + this.selectedEncodingType = Encoding.Label; + } + + ngOnInit(): void { + } + + onNoClick() { + this.dialogRef.close(); + } +} diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.css b/frontend/src/app/_modals/login-modal/login-modal.component.css index e69de29b..f8ffe797 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.css +++ b/frontend/src/app/_modals/login-modal/login-modal.component.css @@ -0,0 +1,37 @@ +.modal-content { + text-align: center; + width: 80%; + margin: auto; +} + +.modal-footer { + text-align: center; +} + +#loginButton { + color: white; + background-color: #003459; + margin-right: 10px; +} + +#loginButton, +#doNotLoginButton { + padding: 2% 6%; +} + +.close-button { + margin: 2%; +} + +#link { + text-decoration: underline; +} + +#link:hover { + color: var(--offwhite); + font-size: 110%; +} + +#wrong-creds { + color: var(--ns-warn); +}
\ No newline at end of file diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.html b/frontend/src/app/_modals/login-modal/login-modal.component.html index 03048155..cea6bf39 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.html +++ b/frontend/src/app/_modals/login-modal/login-modal.component.html @@ -1,42 +1,49 @@ <!-- Modal --> <div class="modal fade" id="modalForLogin" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"> - <div class="modal-dialog modal-dialog-centered"> - <div class="modal-content"> - <div class="modal-header" style="background-color: #003459;"> - <button #closeButton type="button" class="btn-close" style="background-color:white;" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"></button> - </div> - <div class="modal-body px-5" style="color:#003459"> - <h1 class="text-center mt-2 mb-4">Prijavite se</h1> - <form> - <!-- Korisnicko ime --> - <div class="form-outline mb-3"> - <label class="form-label" for="username">Korisničko ime</label> - <input [(ngModel)]="username" name="username" type="text" id="username" - class="form-control form-control" placeholder="Unesite korisničko ime..." /> - </div> - <!-- Lozinka --> - <div class="form-outline mb-3"> - <label class="form-label" for="password">Lozinka</label> - <input [(ngModel)]="password" name="password" type="password" id="password" - class="form-control form-control" placeholder="Unesite lozinku..." /> + <div class="modal-dialog modal-dialog-centered"> + <div class="modal-content bg-alt text-offwhite"> + <button #closeButton type="button" class="close-button btn-clear" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"> + <mat-icon>close</mat-icon> + </button> + <h1 class="login-heading mt-5 mb-5">Prijava</h1> + <form> + <!-- Korisnicko ime --> + <div class="mb-3"> + <mat-form-field appearance="fill"> + <mat-label>Korisničko ime</mat-label> + <input type="text" matInput [(ngModel)]="username" name="username" id="username"> + <mat-icon matSuffix></mat-icon> + </mat-form-field> + </div> + <!-- Lozinka --> + <div class="mb-4"> + <mat-form-field appearance="fill"> + <mat-label>Lozinka</mat-label> + <input type="password" matInput [(ngModel)]="password" name="password" id="pass" #pass> + <ng-container matSuffix *ngIf="!passwordShown"> + <mat-icon (click)="togglePasswordShown()">visibility_off</mat-icon> + </ng-container> + <ng-container matSuffix *ngIf="passwordShown"> + <mat-icon (click)="togglePasswordShown()">visibility</mat-icon> + </ng-container> + </mat-form-field> + </div> + </form> + + <div class="text-lg-start"> + <p *ngIf="wrongCreds" class="small text-center" id="wrong-creds">Unesite ispravno korisničko ime i lozinku.</p> </div> - </form> - - <div class="text-center text-lg-start mt-5"> - <p *ngIf="wrongCreds" class="small fw-bold text-danger text-center">Unesite ispravan e-mail i lozinku.</p> - </div> - <div class="col-md-12 d-flex justify-content-center"> - <button type="button" class="btn btn-lg" style="color:white; background-color: #003459; margin-right: 10px;" (click)="doLogin()">Prijavite se</button> - <button type="button" class="btn btn-lg btn-outline-secondary" data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> - </div> - <br> - </div> - <div class="modal-footer justify-content-center"> - <p class="small fw-bold">Još uvek nemate nalog? - <a data-bs-toggle="modal" data-bs-target="#modalForRegister" class="link-danger">Registrujte se</a> - </p> - </div> + <!--mat-raised-button--> + <div class="d-flex justify-content-center"> + <button mat-raised-button id="loginButton" (click)="doLogin()">Prijavite se</button> + <button mat-stroked-button id="doNotloginButton" data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> + </div> + <div class="modal-footer justify-content-center mt-5"> + <p class="small mt-1">Nemate nalog? + <a data-bs-toggle="modal" data-bs-target="#modalForRegister"><span id="link" (click)="cleanWarnings()">Registrujte se</span></a> + </p> + </div> + </div> </div> - </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.ts b/frontend/src/app/_modals/login-modal/login-modal.component.ts index b28d9799..ccd78509 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.ts +++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts @@ -14,10 +14,13 @@ import {AfterViewInit, ElementRef} from '@angular/core'; export class LoginModalComponent implements OnInit { @ViewChild('closeButton') closeButton?: ElementRef; + @ViewChild('pass') passwordInput!: ElementRef; username: string = ''; password: string = ''; + passwordShown: boolean = false; + wrongCreds: boolean = false; constructor( @@ -37,14 +40,19 @@ export class LoginModalComponent implements OnInit { if (response == "Username doesn't exist" || response == "Wrong password") { this.wrongCreds = true; this.password = ''; + this.passwordShown = false; + this.passwordInput.nativeElement.type = "password"; } else { + this.wrongCreds = false; this.authService.authenticate(response); (<HTMLSelectElement>this.closeButton?.nativeElement).click(); this.userInfoService.getUserInfo().subscribe((response) => { shared.photoId = response.photoId; }); + location.reload(); } + }); } else { @@ -57,4 +65,17 @@ export class LoginModalComponent implements OnInit { this.username = ''; this.password = ''; } + + togglePasswordShown() { + this.passwordShown = !this.passwordShown; + + if (this.passwordShown) + this.passwordInput.nativeElement.type = "text"; + else + this.passwordInput.nativeElement.type = "password"; + } + + cleanWarnings() { + this.wrongCreds = false; + } } diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html new file mode 100644 index 00000000..81aec5f8 --- /dev/null +++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html @@ -0,0 +1,13 @@ +<h1 mat-dialog-title>Popunjavanje nedostajućih vrednosti</h1> +<div mat-dialog-content> + <p>Želim da:</p> + <mat-radio-group [(ngModel)]="selectedMissingValuesOption"> + <mat-radio-button [value]="NullValueOptions.DeleteColumns" checked>obrišem sve kolone koje sadrže nedostajuće vrednosti</mat-radio-button> + <mat-radio-button [value]="NullValueOptions.DeleteRows">obrišem sve redove koji sadrže nedostajuće vrednosti</mat-radio-button> + </mat-radio-group> + <p>Da li ste sigurni u izbor?</p> +</div> +<div mat-dialog-actions> + <button mat-button [mat-dialog-close]="selectedMissingValuesOption" cdkFocusInitial>Da</button> + <button mat-button (click)="onNoClick()">Odustani</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.spec.ts b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.spec.ts new file mode 100644 index 00000000..958925f4 --- /dev/null +++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MissingvaluesDialogComponent } from './missingvalues-dialog.component'; + +describe('MissingvaluesDialogComponent', () => { + let component: MissingvaluesDialogComponent; + let fixture: ComponentFixture<MissingvaluesDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MissingvaluesDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MissingvaluesDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.ts b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.ts new file mode 100644 index 00000000..908edd9e --- /dev/null +++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { NullValueOptions } from 'src/app/_data/Experiment'; + +@Component({ + selector: 'app-missingvalues-dialog', + templateUrl: './missingvalues-dialog.component.html', + styleUrls: ['./missingvalues-dialog.component.css'] +}) +export class MissingvaluesDialogComponent implements OnInit { + + selectedMissingValuesOption?: NullValueOptions; + + NullValueOptions = NullValueOptions; + + constructor(public dialogRef: MatDialogRef<MissingvaluesDialogComponent>) + { + this.selectedMissingValuesOption = NullValueOptions.DeleteColumns; + } + + ngOnInit(): void { + } + + onNoClick() { + this.dialogRef.close(); + } + +} diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.css b/frontend/src/app/_modals/register-modal/register-modal.component.css index e69de29b..f8496973 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.css +++ b/frontend/src/app/_modals/register-modal/register-modal.component.css @@ -0,0 +1,44 @@ +.modal-content { + text-align: center; + margin: auto; +} + +.modal-footer { + text-align: center; +} + +#registerButton { + color: white; + background-color: #003459; + margin-right: 10px; +} + +#registerButton, +#doNotRegisterButton { + padding: 2% 7%; +} + +.close-button { + margin: 2%; +} + +#link { + text-decoration: underline; +} + +#link:hover { + color: var(--offwhite); + font-size: 110%; +} + +.mat-form-field { + width: 80%; +} + +.wrong-creds { + color: var(--ns-warn); +} + +p { + font-size: 11px; +}
\ No newline at end of file diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.html b/frontend/src/app/_modals/register-modal/register-modal.component.html index 68025a46..d76af4d6 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.html +++ b/frontend/src/app/_modals/register-modal/register-modal.component.html @@ -1,86 +1,85 @@ <!-- Modal --> -<div class="modal fade" id="modalForRegister" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" - aria-labelledby="staticBackdropLabel" aria-hidden="true"> - <div class="modal-dialog modal-dialog-centered modal-dialog modal-lg"> - <div class="modal-content"> - <div class="modal-header" style="background-color: #003459;"> - <button id="closeButtonReg" type="button" class="btn-close" data-bs-dismiss="modal" style="background-color: white;" - aria-label="Close" (click)="resetData()"></button> - </div> - <div class="modal-body" style="color:#003459"> - <h1 class="text-center mt-2 mb-4">Registracija</h1> - - <form class="mx-5"> - <!--Ime--> - <div class="row"> - <div class="col-6 px-3 py-3"> - <label class="form-label" for="firstName">Ime</label> - <input type="text" id="firstName" class="form-control" [(ngModel)]="firstName" - name="firstName" placeholder="Unesite ime..."> - <small *ngIf="wrongFirstNameBool" class="form-text text-danger">Unesite ispravno - ime.</small> - </div> - <!--Prezime--> - <div class="col-6 px-3 py-3"> - <label class="form-label" for="lastName">Prezime</label> - <input type="text" id="lastName" class="form-control" [(ngModel)]="lastName" name="lastName" - placeholder="Unesite prezime..." /> - <small *ngIf="wrongLastNameBool" class="form-text text-danger">Unesite ispravno - prezime.</small> - </div> - </div> - <div class="row"> - <!--Korisnicko ime--> - <div class="col-12 px-3 py-3"> - <label class="form-label" for="username-register">Korisničko ime</label> - <input type="text" id="username-register" class="form-control" [(ngModel)]="username" - name="username-register" placeholder="Unesite korisničko ime..." /> - <small *ngIf="wrongUsernameBool" class="form-text text-danger">Unesite ispravno korisničko - ime.</small> - </div> - </div> - <div class="row"> - <!--Email--> - <div class="col-12 px-3 py-3"> - <label class="form-label" for="email">E-mail adresa</label> - <input type="email" id="email" class="form-control" [(ngModel)]="email" name="email" - placeholder="Unesite email adresu..." /> - <small *ngIf="wrongEmailBool" class="form-text text-danger">Unesite ispravno e-mail - adresu.</small> - </div> - </div> - <div class="row"> - <!-- Lozinka 1. --> - <div class="col-6 px-3 py-3"> - <label class="form-label" for="pass1">Lozinka</label> - <input type="password" id="pass1" class="form-control" [(ngModel)]="pass1" name="pass1" - placeholder="Unesite lozinku..." /> - <small *ngIf="wrongPass1Bool" class="form-text text-danger">Lozinka se mora sastojati od - najmanje 6 karaktera.</small> - </div> - <!-- Lozinka 2. --> - <div class="col-6 px-3 py-3"> - <label class="form-label" for="pass2">Potvrdite lozinku</label> - <input type="password" id="pass2" class="form-control" [(ngModel)]="pass2" name="pass2" - placeholder="Ponovite lozinku..." /> - <small *ngIf="wrongPass2Bool" class="form-text text-danger">Lozinke se ne - podudaraju.</small> - </div> - </div> - </form> - <div class="col-md-12 d-flex justify-content-center mt-5"> - <button type="button" class="btn btn-lg" - style="color:white; background-color: #003459; margin-right: 10px;" - (click)="doRegister()">Registrujte se</button> - <button type="button" class="btn btn-lg btn-outline-secondary" style="margin-left: 15px;" - data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> +<div class="modal fade" id="modalForRegister" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog modal-dialog-centered"> + <div class="modal-content bg-alt text-offwhite"> + <button #closeButton type="button" class="close-button btn-clear" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"> + <mat-icon>close</mat-icon> + </button> + <h1 class="mt-5 mb-4">Registracija</h1> + <form class="mx-4"> + <!--Ime--> + <div> + <mat-form-field appearance="fill"> + <mat-label>Ime</mat-label> + <input type="text" matInput [(ngModel)]="firstName" name="firstName" id="firstName"> + <mat-icon matSuffix></mat-icon> + </mat-form-field> + <p *ngIf="wrongFirstNameBool" class="wrong-creds">Unesite ispravno ime.</p> + </div> + <!--Prezime--> + <div> + <mat-form-field appearance="fill"> + <mat-label>Prezime</mat-label> + <input type="text" matInput [(ngModel)]="lastName" name="lastName" id="lastName"> + <mat-icon matSuffix></mat-icon> + </mat-form-field> + <p *ngIf="wrongLastNameBool" class="wrong-creds">Unesite ispravno prezime.</p> + </div> + <!--Korisnicko ime--> + <div> + <mat-form-field appearance="fill"> + <mat-label>Korisničko ime</mat-label> + <input type="text" matInput [(ngModel)]="username" name="username-register" id="username-register"> + <mat-icon matSuffix></mat-icon> + </mat-form-field> + <p *ngIf="wrongUsernameBool" class="wrong-creds">Unesite ispravno korisničko ime.</p> + </div> + <!--Email--> + <div> + <mat-form-field appearance="fill"> + <mat-label>E-mail adresa</mat-label> + <input type="email" matInput [(ngModel)]="email" name="email" id="email"> + <mat-icon matSuffix></mat-icon> + </mat-form-field> + <p *ngIf="wrongEmailBool" class="wrong-creds">Unesite ispravno e-mail adresu.</p> + </div> + <!-- Lozinka 1. --> + <div> + <mat-form-field appearance="fill"> + <mat-label>Lozinka</mat-label> + <input type="password" matInput [(ngModel)]="pass1" name="pass1" id="pass1" #password1> + <ng-container matSuffix *ngIf="!password1Shown"> + <mat-icon (click)="togglePasswordShown(1)">visibility_off</mat-icon> + </ng-container> + <ng-container matSuffix *ngIf="password1Shown"> + <mat-icon (click)="togglePasswordShown(1)">visibility</mat-icon> + </ng-container> + </mat-form-field> + <p *ngIf="wrongPass1Bool" class="wrong-creds">Lozinka se mora sastojati od najmanje 6 karaktera.</p> + </div> + <!-- Lozinka 2. --> + <div> + <mat-form-field appearance="fill"> + <mat-label>Potvrdite lozinku</mat-label> + <input type="password" matInput [(ngModel)]="pass2" name="pass2" id="pass2" #password2> + <ng-container matSuffix *ngIf="!password2Shown"> + <mat-icon (click)="togglePasswordShown(2)">visibility_off</mat-icon> + </ng-container> + <ng-container matSuffix *ngIf="password2Shown"> + <mat-icon (click)="togglePasswordShown(2)">visibility</mat-icon> + </ng-container> + </mat-form-field> + <p *ngIf="wrongPass2Bool" class="wrong-creds">Lozinke se ne podudaraju.</p> </div> - <br> + </form> + <div class="d-flex justify-content-center mt-2"> + <button mat-raised-button id="registerButton" (click)="doRegister()">Registrujte se</button> + <button mat-stroked-button id="doNotRegisterButton" data-bs-dismiss="modal" (click)="resetData()">Odustanite</button> </div> - <div class="modal-footer justify-content-center"> - <p class="small fw-bold">Već imate kreiran nalog? - <a id="linkToLoginModal" data-bs-toggle="modal" data-bs-target="#modalForLogin" - class="link-danger">Prijavite se</a> + <br> + <div class="modal-footer justify-content-center mt-3"> + <p class="small">Imate kreiran nalog? + <a data-bs-toggle="modal" data-bs-target="#modalForLogin"><span id="link" (click)="cleanWarnings()">Prijavite se</span></a> </p> </div> </div> diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.ts b/frontend/src/app/_modals/register-modal/register-modal.component.ts index 11f6727d..9da5484d 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.ts +++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { AuthService } from 'src/app/_services/auth.service'; import User from 'src/app/_data/User'; import { DOCUMENT } from '@angular/common'; @@ -34,6 +34,13 @@ export class RegisterModalComponent implements OnInit { shared = shared; + password1Shown: boolean = false; + password2Shown: boolean = false; + @ViewChild('password1') password1Input!: ElementRef; + @ViewChild('password2') password2Input!: ElementRef; + @ViewChild('closeButton') closeButton!: ElementRef; + + constructor( private authService: AuthService, @Inject(DOCUMENT) document: Document @@ -48,6 +55,8 @@ export class RegisterModalComponent implements OnInit { resetData() { this.firstName = this.lastName = this.username = this.email = this.pass1 = this.pass2 = ''; this.wrongFirstNameBool = this.wrongLastNameBool = this.wrongUsernameBool = this.wrongEmailBool = this.wrongPass1Bool = this.wrongPass2Bool = false; + this.password1Shown = false; + this.password2Shown = false; } isCorrectName(element: string): boolean { @@ -76,7 +85,7 @@ export class RegisterModalComponent implements OnInit { this.wrongFirstNameBool = false; return; } - (<HTMLSelectElement>document.getElementById('firstName')).focus(); + //(<HTMLSelectElement>document.getElementById('firstName')).focus(); this.wrongFirstNameBool = true; } lastNameValidation() { @@ -84,7 +93,7 @@ export class RegisterModalComponent implements OnInit { this.wrongLastNameBool = false; return; } - (<HTMLSelectElement>document.getElementById('lastName')).focus(); + //(<HTMLSelectElement>document.getElementById('lastName')).focus(); this.wrongLastNameBool = true; } usernameValidation() { @@ -92,7 +101,7 @@ export class RegisterModalComponent implements OnInit { this.wrongUsernameBool = false; return; } - (<HTMLSelectElement>document.getElementById('username-register')).focus(); + //(<HTMLSelectElement>document.getElementById('username-register')).focus(); this.wrongUsernameBool = true; } emailValidation() { @@ -100,7 +109,7 @@ export class RegisterModalComponent implements OnInit { this.wrongEmailBool = false; return; } - (<HTMLSelectElement>document.getElementById('email')).focus(); + //(<HTMLSelectElement>document.getElementById('email')).focus(); this.wrongEmailBool = true; } passwordValidation() { @@ -111,7 +120,11 @@ export class RegisterModalComponent implements OnInit { } this.pass1 = ''; //brisi obe ukucane lozinke this.pass2 = ''; - (<HTMLSelectElement>document.getElementById('pass1')).focus(); + this.password1Shown = false; + this.password2Shown = false; + this.password1Input.nativeElement.type = "password"; + this.password2Input.nativeElement.type = "password"; + //(<HTMLSelectElement>document.getElementById('pass1')).focus(); this.wrongPass1Bool = true; this.wrongPass2Bool = true; } @@ -148,7 +161,8 @@ export class RegisterModalComponent implements OnInit { this.authService.login(this.username, this.pass1).subscribe((response) => { this.authService.authenticate(response); - (<HTMLSelectElement>document.getElementById('closeButtonReg')).click(); + this.closeButton.nativeElement.click(); + location.reload(); //(<HTMLSelectElement>document.getElementById('linkToLoginModal')).click(); }, (error) => console.warn(error)); } @@ -165,5 +179,25 @@ export class RegisterModalComponent implements OnInit { } } + togglePasswordShown(whichPassword: number) { + if (whichPassword == 1) { + this.password1Shown = !this.password1Shown; + + if (this.password1Shown) + this.password1Input.nativeElement.type = "text"; + else + this.password1Input.nativeElement.type = "password"; + } + else { + this.password2Shown = !this.password2Shown; + if (this.password2Shown) + this.password2Input.nativeElement.type = "text"; + else + this.password2Input.nativeElement.type = "password"; + } + } + cleanWarnings() { + this.resetData(); + } } diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html new file mode 100644 index 00000000..bac73e0a --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html @@ -0,0 +1,13 @@ +<h1 mat-dialog-title>Čuvanje eksperimenta</h1> +<div mat-dialog-content> + <span>Unesi naziv eksperimenta:</span> + <mat-form-field> + <input type="text" matInput [(ngModel)]="selectedName"> + </mat-form-field> + <br><br> + <p>Sačuvaj eksperiment:</p> +</div> +<div mat-dialog-actions> + <button mat-button [mat-dialog-close]="selectedName" cdkFocusInitial>Da</button> + <button mat-button (click)="onNoClick()">Odustani</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts new file mode 100644 index 00000000..5fd6cb71 --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaveExperimentDialogComponent } from './save-experiment-dialog.component'; + +describe('SaveExperimentDialogComponent', () => { + let component: SaveExperimentDialogComponent; + let fixture: ComponentFixture<SaveExperimentDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaveExperimentDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SaveExperimentDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts new file mode 100644 index 00000000..ca01f57e --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-save-experiment-dialog', + templateUrl: './save-experiment-dialog.component.html', + styleUrls: ['./save-experiment-dialog.component.css'] +}) +export class SaveExperimentDialogComponent implements OnInit { + + selectedName: string = ''; + + constructor(public dialogRef: MatDialogRef<SaveExperimentDialogComponent>) { } + + ngOnInit(): void { + } + + onNoClick() { + this.dialogRef.close(); + } +} diff --git a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html index 572e8c4c..06e74093 100644 --- a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html +++ b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html @@ -1,8 +1,8 @@ <h2 mat-dialog-title class="text-muted">{{data.title}}</h2> <div mat-dialog-content class="mt-4" style="color: rgb(81, 76, 76);"> - {{data.message}} + {{data.message}} </div> <div mat-dialog-actions class="d-flex justify-content-center mt-4"> - <button mat-button cdkFocusInitial (click)="onYesClick()" class="btn-lg" style="background-color: #003459; color:white;">Da</button> - <button mat-button cdkFocusInitial (click)="onNoClick()" class="btn-lg" style="background-color: lightgray;">Ne</button> + <button mat-button cdkFocusInitial (click)="onYesClick()" style="background-color: lightgray;">Da</button> + <button mat-button cdkFocusInitial (click)="onNoClick()" style="background-color: lightgray;">Ne</button> </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/archive/archive.component.css b/frontend/src/app/_pages/archive/archive.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/archive/archive.component.css diff --git a/frontend/src/app/_pages/archive/archive.component.html b/frontend/src/app/_pages/archive/archive.component.html new file mode 100644 index 00000000..fc3c4763 --- /dev/null +++ b/frontend/src/app/_pages/archive/archive.component.html @@ -0,0 +1,49 @@ +<div class="d-flex flex-column align-items-center my-5"> + <app-folder></app-folder> + + <!--<div class="my-5" style="height: fit-content;"> + <app-playlist [datasets]="publicDatasets"></app-playlist> + </div>--> + + <!--<div id="cards" class="row align-items-view align-items-stretch justify-content-center"> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training + </mat-icon> + <h3 class="card-title my-2">Moji eksperimenti</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-models">Pregledajte</a> vaše modele, menjajte ih, napravite nove modele, ili ih obrišite. + </p> + </div> + </div> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">storage</mat-icon> + <h3 class="card-title my-2">Izvori podataka</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-datasets">Preuredite</a> vaše izvore podataka, ili dodajte novi. + </p> + </div> + </div> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training + </mat-icon> + <h3 class="card-title my-2">Modeli</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-models">Pregledajte</a> vaše modele, menjajte ih, napravite nove modele, ili ih obrišite. + </p> + </div> + </div> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">batch_prediction + </mat-icon> + <h3 class="card-title my-2">Rezultati treniranja</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="my-predictors">Pregledajte</a> sve vaše trenirane modele, koristite ih da predvidite vrednosti za red ili skup podataka, ili ih obrišite. + </p> + </div> + </div> + </div>--> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/archive/archive.component.spec.ts b/frontend/src/app/_pages/archive/archive.component.spec.ts new file mode 100644 index 00000000..41fc8e77 --- /dev/null +++ b/frontend/src/app/_pages/archive/archive.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ArchiveComponent } from './archive.component'; + +describe('ArchiveComponent', () => { + let component: ArchiveComponent; + let fixture: ComponentFixture<ArchiveComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArchiveComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ArchiveComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/archive/archive.component.ts b/frontend/src/app/_pages/archive/archive.component.ts new file mode 100644 index 00000000..47f96218 --- /dev/null +++ b/frontend/src/app/_pages/archive/archive.component.ts @@ -0,0 +1,25 @@ +import { Component, OnInit } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import { DatasetsService } from 'src/app/_services/datasets.service'; + +@Component({ + selector: 'app-archive', + templateUrl: './archive.component.html', + styleUrls: ['./archive.component.css'] +}) +export class ArchiveComponent implements OnInit { + + publicDatasets: Dataset[] = []; + + constructor(private datasetsService: DatasetsService) { } + + ngOnInit(): void { + this.datasetsService.getPublicDatasets().subscribe((datasets) => { + this.publicDatasets = datasets; + this.publicDatasets.forEach((element, index) => { + this.publicDatasets[index] = (<Dataset>element); + }) + }); + } + +} diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html deleted file mode 100644 index fa38a1bc..00000000 --- a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.html +++ /dev/null @@ -1 +0,0 @@ -<p>browse-datasets works!</p> diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.spec.ts b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.spec.ts deleted file mode 100644 index fda74dbe..00000000 --- a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BrowseDatasetsComponent } from './browse-datasets.component'; - -describe('BrowseDatasetsComponent', () => { - let component: BrowseDatasetsComponent; - let fixture: ComponentFixture<BrowseDatasetsComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ BrowseDatasetsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(BrowseDatasetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts b/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts deleted file mode 100644 index dba6c25e..00000000 --- a/frontend/src/app/_pages/browse-datasets/browse-datasets.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-browse-datasets', - templateUrl: './browse-datasets.component.html', - styleUrls: ['./browse-datasets.component.css'] -}) -export class BrowseDatasetsComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css deleted file mode 100644 index b4ac9669..00000000 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.css +++ /dev/null @@ -1,7 +0,0 @@ -#container { - border-radius: 8px; -} - -#wrapper { - color: #003459; -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html deleted file mode 100644 index 27e06884..00000000 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html +++ /dev/null @@ -1,40 +0,0 @@ - -<div id="wrapper"> - - <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - <div class="row mt-3 mb-2 d-flex justify-content-center"> - - <div class="col-sm-6" style="margin-bottom: 10px;"> - <p class="glyphicon glyphicon-search"></p> - <input type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> - - </div> - - <div class="row"> - <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let predictor of publicPredictors | filter:term"> - <div class="card h-100"> - <div class="card-body"> - <h3 class="card-title"><b>{{predictor.name}}</b></h3> - <p class="card-text">{{predictor.description}}</p> - <a class="btn btn-primary" (click)="openPredictor(predictor._id)">Iskoristi</a> - </div> - <div class="card-footer text-muted"> - Kreirao: {{predictor.username}} <br> - Datum kreiranja: {{predictor.dateCreated |date}} - </div> - </div> - </div> - - - </div> - <div class="text-center"*ngIf="( publicPredictors != undefined && publicPredictors|filter:term).length === 0"> - <h2>Nema rezultata</h2> - </div> - </div> - - </div> - - - - -</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts deleted file mode 100644 index 6d13fedf..00000000 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BrowsePredictorsComponent } from './browse-predictors.component'; - -describe('BrowsePredictorsComponent', () => { - let component: BrowsePredictorsComponent; - let fixture: ComponentFixture<BrowsePredictorsComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ BrowsePredictorsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(BrowsePredictorsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts deleted file mode 100644 index 891b3cab..00000000 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { PredictorsService } from 'src/app/_services/predictors.service'; -import Predictor from 'src/app/_data/Predictor'; -import {Router} from '@angular/router' -@Component({ - selector: 'app-browse-predictors', - templateUrl: './browse-predictors.component.html', - styleUrls: ['./browse-predictors.component.css'] -}) -export class BrowsePredictorsComponent implements OnInit { - - publicPredictors? :Predictor[]; - term: string=""; - constructor(private predictors: PredictorsService,private router:Router) { - this.predictors.getPublicPredictors().subscribe((predictors) => { - this.publicPredictors = predictors; - }); - } - - ngOnInit(): void { - } - openPredictor(id:string):void{ - this.router.navigate(['predict/'+id]); - }; - -} diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css new file mode 100644 index 00000000..36c35484 --- /dev/null +++ b/frontend/src/app/_pages/experiment/experiment.component.css @@ -0,0 +1,55 @@ +ul { + list-style: none; +} + +.holder { + display: flex; + flex-direction: row; + align-items: stretch; +} + +.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 { + background-color: transparent; +} + +.label { + color: white; +} + +.steps-container { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + overflow-y: auto; +} + +.step-content { + position: relative; + width: 100%; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.step-content-inside { + width: 98%; + height: 98%; + overflow-y: auto; +}
\ 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 new file mode 100644 index 00000000..2b32db81 --- /dev/null +++ b/frontend/src/app/_pages/experiment/experiment.component.html @@ -0,0 +1,49 @@ +<div class="container-fluid p-0 text-offwhite holder" style="height: calc(100vh - 64px); text-align: center;"> + <div class="d-flex flex-colum align-items-center sidenav"> + <mat-stepper orientation="vertical" (selectionChange)="changePage($event)"> + <mat-step> + <!--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> + </mat-step> + <mat-step> + <ng-template matStepLabel><span class="label">Treniranje</span></ng-template> + <p>Odaberite parametre i trenirajte model</p> + </mat-step> + <mat-step> + <ng-template matStepLabel><span class="label">Pregled rezultata treniranja</span></ng-template> + <p>Pregledajte tok treniranja i grafički prikaz rezultata</p> + </mat-step> + </mat-stepper> + </div> + <div #stepsContainer class="steps-container"> + <div #steps id="step_1" class="step-content"> + <div class="step-content-inside"> + <app-folder #folderDataset [type]="FolderType.Dataset" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyDatasets, TabType.PublicDatasets]" (okPressed)="goToPage(1)" (selectedFileChanged)="setDataset($event)"></app-folder> + </div> + </div> + <div #steps id="step_2" class="step-content"> + <div class="step-content-inside"> + <app-column-table (okPressed)="goToPage(2); experiment._columnsSelected = true;" (columnTableChanged)="columnTableChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table> + </div> + </div> + <div #steps id="step_3" class="step-content"> + <div class="step-content-inside"> + <app-folder #folderModel [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyModels]" (okPressed)="goToPage(3); trainModel();" (selectedFileChanged)="setModel($event)"></app-folder> + </div> + </div> + <div #steps id="step_4" class="step-content"> + <div class="step-content-inside"> + <app-metric-view #metricView></app-metric-view> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/experiment/experiment.component.spec.ts b/frontend/src/app/_pages/experiment/experiment.component.spec.ts index fd2bbd30..fd2bbd30 100644 --- a/frontend/src/app/experiment/experiment.component.spec.ts +++ b/frontend/src/app/_pages/experiment/experiment.component.spec.ts diff --git a/frontend/src/app/_pages/experiment/experiment.component.ts b/frontend/src/app/_pages/experiment/experiment.component.ts new file mode 100644 index 00000000..1dc18a78 --- /dev/null +++ b/frontend/src/app/_pages/experiment/experiment.component.ts @@ -0,0 +1,164 @@ +import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren, Input } from '@angular/core'; +import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { MatStepper } from '@angular/material/stepper'; +import Shared from 'src/app/Shared'; +import { FolderFile, FolderType } from 'src/app/_data/FolderFile'; +import { FolderComponent, TabType } from 'src/app/_elements/folder/folder.component'; +import Experiment from 'src/app/_data/Experiment'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; +import { ModelsService } from 'src/app/_services/models.service'; +import Model from 'src/app/_data/Model'; +import Dataset from 'src/app/_data/Dataset'; +import { ColumnTableComponent } from 'src/app/_elements/column-table/column-table.component'; +import { SignalRService } from 'src/app/_services/signal-r.service'; +import { MetricViewComponent } from 'src/app/_elements/metric-view/metric-view.component'; + +@Component({ + selector: 'app-experiment', + templateUrl: './experiment.component.html', + styleUrls: ['./experiment.component.css'] +}) +export class ExperimentComponent implements AfterViewInit { + + @ViewChild(MatStepper) stepper!: MatStepper; + @ViewChild('stepsContainer') stepsContainer!: ElementRef; + @ViewChildren('steps') steps!: ElementRef[]; + + event: number = 0; + experiment: Experiment; + dataset?: Dataset; + @ViewChild("folderDataset") folderDataset!: FolderComponent; + @ViewChild(ColumnTableComponent) columnTable!: ColumnTableComponent; + @ViewChild("folderModel") folderModel!: FolderComponent; + @ViewChild("metricView") metricView!: MetricViewComponent; + + constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService, private signalRService: SignalRService) { + this.experiment = new Experiment("exp1"); + } + + /*updateExperiment(){ + + }*/ + + addNewExperiment() { + this.experimentsService.addExperiment(this.experiment).subscribe(() => { console.log("new Experiment") }); + } + + trainModel() { + if (!this.modelToTrain) { + Shared.openDialog('Greška', 'Morate odabrati konfiguraciju neuronske mreže'); + } else { + this.modelsService.trainModel(this.modelToTrain._id, this.experiment._id).subscribe(() => { console.log("pocelo treniranje") }); + } + } + + 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); + }); + + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { + console.log(this.modelToTrain?._id, mId); + if (currentEpoch == 0) { + this.history = []; + } + if (this.modelToTrain?._id == mId) { + stat = stat.replace(/'/g, '"'); + //console.log('JSON', this.trainingResult); + this.history.push(JSON.parse(stat)); + this.metricView.update(this.history); + } + }); + + } + } + + history: any[] = []; + + 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) + } + + 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; + + FolderType = FolderType; + + TabType = TabType; + + columnTableChangedEvent() { + //sta se desi kad se nesto promeni u column-table komponenti... + //console.log("promenio se column-table"); + } + + setDataset(dataset: FolderFile) { + const d = <Dataset>dataset; + this.experiment.datasetId = d._id; + this.dataset = d; + + this.columnTable.loadDataset(this.dataset); + } + + modelToTrain?: Model; + + setModel(model: FolderFile) { + const m = <Model>model; + this.modelToTrain = m; + } +} diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.html b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.html deleted file mode 100644 index 84f5ebaf..00000000 --- a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.html +++ /dev/null @@ -1,38 +0,0 @@ - -<div id="wrapper"> - - <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - <div class="row mt-3 mb-2 d-flex justify-content-center"> - - <div class="col-sm-6" style="margin-bottom: 10px;"> - <input type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> - </div> - - <div class="row"> - <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let dataset of publicDatasets | filter:term"> - <div class="card h-100"> - <div class="card-body"> - <h3 class="card-title"><b>{{dataset.name}}</b></h3> - <p class="card-text">{{dataset.description}}</p> - <a class="btn btn-primary" (click)="addDataset(dataset)">Dodaj dataset</a> - </div> - <div class="card-footer text-muted"> - Kreirao: {{dataset.username}} <br> - Datum kreiranja: {{dataset.dateCreated |date}} - </div> - </div> - </div> - - - </div> - <div class="text-center"*ngIf="( publicDatasets != undefined && publicDatasets|filter:term).length === 0"> - <h2>Nema rezultata</h2> - </div> - </div> - - </div> - - - - -</div> diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.spec.ts b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.spec.ts deleted file mode 100644 index 6ab894fd..00000000 --- a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FilterDatasetsComponent } from './filter-datasets.component'; - -describe('FilterDatasetsComponent', () => { - let component: FilterDatasetsComponent; - let fixture: ComponentFixture<FilterDatasetsComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ FilterDatasetsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(FilterDatasetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_pages/home/home.component.css b/frontend/src/app/_pages/home/home.component.css index e69de29b..906f5728 100644 --- a/frontend/src/app/_pages/home/home.component.css +++ b/frontend/src/app/_pages/home/home.component.css @@ -0,0 +1,20 @@ +.logo { + margin: 0 !important; +} + +#title { + color: var(--offwhite); +} + +h1 { + font-size: 64px !important; + font-weight: 900 !important; + margin-top: 1rem; + margin-bottom: 2.5rem; +} + +.card { + margin: 2.5rem !important; + padding: 1.5rem; + width: 26rem !important; +}
\ No newline at end of file diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html index f5e94d27..2825b3bf 100644 --- a/frontend/src/app/_pages/home/home.component.html +++ b/frontend/src/app/_pages/home/home.component.html @@ -1,57 +1,33 @@ -<div class="d-flex flex-column align-items-center bg-light"> - <img src="../../../assets/svg/logo.svg" class="bi me-2" width="256" height="256" role="img"> - <div *ngIf="shared.loggedIn" class="d-flex flex-column align-items-center"> - <h2 class="my-4">Započnite sa treniranjem!</h2> - <div id="cards" class="row align-items-stretch justify-content-center"> - <div class="card shadow col-3 m-1" style="width: 18rem;"> - <div class="card-body"> - <mat-icon width="48px" height="48px" - style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">storage</mat-icon> - <h3 class="card-title my-2">Moji izvori podataka</h3> - <p class="card-text"> - <a class="stretched-link" routerLink="my-datasets">Preuredite</a> vaše izvore - podataka, ili - dodajte novi. - </p> - </div> - </div> - <div class="card shadow col-3 m-1" style="width: 18rem;"> - <div class="card-body"> - <mat-icon width="48px" height="48px" - style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training - </mat-icon> - <h3 class="card-title my-2">Moji modeli</h3> - <p class="card-text"> - <a class="stretched-link" routerLink="my-models">Pregledajte</a> vaše modele, menjajte ih, - napravite nove modele, ili - ih obrišite. - </p> - </div> +<div class="d-flex flex-column align-items-center"> + <div class="logo"> + <img src="../../../assets/images/logo.png" class="bi me-2" width="256" height="256" role="img"> + </div> + + <div id="title"> + <h1>Igr<span class="highlight">ann</span>onica</h1> + </div> + + <div id="cards" class="row align-items-view align-items-stretch justify-content-center"> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training</mat-icon> + <h3 class="card-title my-2">Experimentiši</h3> + <p class="card-text"> + U tri koraka <a class="stretched-link" routerLink="experiment">napravite novu neuronsku mrežu</a>. Koristite postojeće izvore podataka, modele, itd. + </p> </div> - <div class="card shadow col-3 m-1" style="width: 18rem;"> - <div class="card-body"> - <mat-icon width="48px" height="48px" - style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">batch_prediction - </mat-icon> - <h3 class="card-title my-2">Rezultati treniranja</h3> - <p class="card-text"> - <a class="stretched-link" routerLink="my-predictors">Pregledajte</a> sve vaše trenirane - modele, - koristite ih da predvidite vrednosti za red ili skup podataka, ili ih obrišite. - </p> - </div> + </div> + <div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;"> + <div class="card-body"> + <mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">storage + </mat-icon> + <h3 class="card-title my-2">Arhiva</h3> + <p class="card-text"> + <a class="stretched-link" routerLink="archive">Upravljajte</a> izvorima podataka, eksperimentima, modelima, i rezultatima treniranja. Pogledajte podatke koje su podelili drugi korisnici. + </p> </div> </div> - </div> - <h2 class="my-4">Pogledajte javne izvore podataka!</h2> - <app-carousel [items]="publicDatasets" [type]="'Dataset'"> - - </app-carousel> - <h3><a routerLink="browse-datasets">Pogledaj sve javne izvore podataka...</a></h3> - <h2 class="my-4">Iskoristite već trenirane modele!</h2> - <app-carousel [items]="publicPredictors" [type]="'Predictor'"> - </app-carousel> - <h3><a routerLink="browse-predictors">Pogledaj sve javne trenirane modele...</a></h3> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/home/home.component.ts b/frontend/src/app/_pages/home/home.component.ts index 0575c4c0..28ba2cbb 100644 --- a/frontend/src/app/_pages/home/home.component.ts +++ b/frontend/src/app/_pages/home/home.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit } from '@angular/core'; import Dataset from 'src/app/_data/Dataset'; import Predictor from 'src/app/_data/Predictor'; -import { ItemDatasetComponent } from 'src/app/_elements/item-dataset/item-dataset.component'; import shared from 'src/app/Shared'; import { DatasetsService } from 'src/app/_services/datasets.service'; import { PredictorsService } from 'src/app/_services/predictors.service'; @@ -14,7 +13,6 @@ import { PredictorsService } from 'src/app/_services/predictors.service'; export class HomeComponent implements OnInit { publicDatasets: Dataset[] = []; - publicPredictors: Predictor[] = []; shared = shared; @@ -25,9 +23,6 @@ export class HomeComponent implements OnInit { this.publicDatasets[index] = (<Dataset>element); }) }); - this.predictorsService.getPublicPredictors().subscribe((predictors) => { - this.publicPredictors = predictors; - }); } ngOnInit(): void { diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.css b/frontend/src/app/_pages/my-datasets/my-datasets.component.css deleted file mode 100644 index 57889937..00000000 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.css +++ /dev/null @@ -1,8 +0,0 @@ -#header { - background-color: #003459; - padding-top: 20px; - padding-bottom: 15px; - text-align: center; - color: white; - border-radius: 5px; -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.html b/frontend/src/app/_pages/my-datasets/my-datasets.component.html deleted file mode 100644 index 0c83dc85..00000000 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.html +++ /dev/null @@ -1,39 +0,0 @@ -<div id="header"> - <h1>Moji setovi podataka</h1> -</div> -<div id="wrapper"> - <div id="container" class="container p-5" style="background-color: rgba(255, 255, 255, 0.8); min-height: 100%;"> - <div class="row mt-3 mb-2 d-flex justify-content-center"> - - <div class="col-sm-6" style="margin-bottom: 10px;"> - </div> - - <div class="row"> - <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let dataset of myDatasets"> - <app-item-dataset [dataset]="dataset"></app-item-dataset> - - <div class="panel-footer row"><!-- panel-footer --> - <div class="col-xs-6 text-center"> - <div> - <div> - <button (click)="deleteThisDataset(dataset)" mat-raised-button color="warn" style="min-width: 10rem;float: right" ><mat-icon>delete</mat-icon></button> - </div> - - </div> - </div> - </div><!-- end panel-footer --> - </div> - </div> - <div class="text-center" *ngIf="this.myDatasets.length == 0" > - <h2>Nema rezultata</h2> - </div> - </div> - - - </div> - - - - - - </div> diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts b/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts deleted file mode 100644 index fc1fc3f3..00000000 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MyDatasetsComponent } from './my-datasets.component'; - -describe('MyDatasetsComponent', () => { - let component: MyDatasetsComponent; - let fixture: ComponentFixture<MyDatasetsComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ MyDatasetsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(MyDatasetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.ts b/frontend/src/app/_pages/my-datasets/my-datasets.component.ts deleted file mode 100644 index 8857e371..00000000 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import {Router} from '@angular/router'; -import { DatasetsService } from 'src/app/_services/datasets.service'; -import Dataset from 'src/app/_data/Dataset'; -import { JwtHelperService } from '@auth0/angular-jwt'; -import { CookieService } from 'ngx-cookie-service'; -import shared from 'src/app/Shared'; -import { share } from 'rxjs'; - -@Component({ - selector: 'app-my-datasets', - templateUrl: './my-datasets.component.html', - styleUrls: ['./my-datasets.component.css'] -}) -export class MyDatasetsComponent implements OnInit { - myDatasets: Dataset[] = []; - - constructor(private datasetsS : DatasetsService) { - - - - } - - ngOnInit(): void { - - this.datasetsS.getMyDatasets().subscribe((response) => { - this.myDatasets = response; - }, (error) => { - if (error.error == "Dataset with...") { - shared.openDialog("Greska", "Niste dobro uneli nesto"); - } - }); - } - -/* - editModel(): void{ - this.modelsS.editModel().subscribe(m => { - this.myModel = m; - - }) - } -*/ - -deleteThisDataset(dataset: Dataset): void{ - shared.openYesNoDialog('Brisanje seta podataka','Da li ste sigurni da želite da obrišete ovaj set podataka?',() => { - this.datasetsS.deleteDataset(dataset).subscribe((response) => { - this.getAllMyDatasets(); - }, (error) =>{ - if (error.error == "Dataset with name = {name} deleted") { - shared.openDialog("Greška","Greška pri brisanju dataseta!"); - } - }); - }); -} - - getAllMyDatasets(): void{ - this.datasetsS.getMyDatasets().subscribe(m => { - this.myDatasets = m; - }); - } - - -} diff --git a/frontend/src/app/_pages/my-models/my-models.component.css b/frontend/src/app/_pages/my-models/my-models.component.css deleted file mode 100644 index 19d29595..00000000 --- a/frontend/src/app/_pages/my-models/my-models.component.css +++ /dev/null @@ -1,12 +0,0 @@ -button{ - margin-left: 5%; - margin-right: 5%; -} -#header { - background-color: #003459; - padding-top: 20px; - padding-bottom: 15px; - text-align: center; - color: white; - border-radius: 5px; -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.css b/frontend/src/app/_pages/my-predictors/my-predictors.component.css deleted file mode 100644 index ccb9fb7b..00000000 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.css +++ /dev/null @@ -1,13 +0,0 @@ -#header { - background-color: #003459; - padding-top: 20px; - padding-bottom: 15px; - text-align: center; - color: white; - border-radius: 5px; -} - -.row{ - margin-top: 10px; - margin-bottom: 30px; -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.html b/frontend/src/app/_pages/my-predictors/my-predictors.component.html deleted file mode 100644 index 31fa786c..00000000 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.html +++ /dev/null @@ -1,23 +0,0 @@ -<div id="header"> - <h1>Trenirani modeli</h1> -</div> -<div id="wrapper"> -<div id="container" class="container p-5" style="background-color:rgba(255, 255, 255, 0.8); min-height: 100%;"> - <div class="row mt-3 mb-2 d-flex justify-content-center"> - - <div class="col-sm-6" style="margin-bottom: 10px;"> - </div> - <div class="row"> - <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let predictor of predictors"> - <app-item-predictor [predictor]="predictor"></app-item-predictor> - <div> - <button (click)="deleteThisPredictor(predictor)" mat-raised-button color="warn" style="min-width: 10rem;float: right" ><mat-icon>delete</mat-icon></button> - </div> - </div> - </div> -</div> -</div> -</div> - - - diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.spec.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.spec.ts deleted file mode 100644 index 37dddf6d..00000000 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MyPredictorsComponent } from './my-predictors.component'; - -describe('MyPredictorsComponent', () => { - let component: MyPredictorsComponent; - let fixture: ComponentFixture<MyPredictorsComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ MyPredictorsComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(MyPredictorsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts deleted file mode 100644 index 4dc5300d..00000000 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import Predictor from 'src/app/_data/Predictor'; -import { PredictorsService } from 'src/app/_services/predictors.service'; -import shared from 'src/app/Shared'; -@Component({ - selector: 'app-my-predictors', - templateUrl: './my-predictors.component.html', - styleUrls: ['./my-predictors.component.css'] -}) -export class MyPredictorsComponent implements OnInit { - predictors: Predictor[] = []; - constructor(private predictorsS : PredictorsService) { - } - ngOnInit(): void { - this.predictorsS.getMyPredictors().subscribe((response) => { - this.predictors = response; - }, (error) => { - if (error.error == "Predictor with...") { - shared.openDialog("Greska", "Greska"); - } - }); - } - - deleteThisPredictor(predictor: Predictor): void{ - shared.openYesNoDialog('Brisanje prediktora','Da li ste sigurni da želite da obrišete prediktor?',() => { - this.predictorsS.deletePredictor(predictor).subscribe((response) => { - this.getAllMyPredictors(); - }, (error) =>{ - if (error.error == "Predictor with name = {name} deleted") { - shared.openDialog("Obaveštenje", "Greška prilikom brisanja prediktora."); - } - }); - }); - } - - getAllMyPredictors(): void{ - this.predictorsS.getMyPredictors().subscribe(p => { - this.predictors = p; - }); - } - - -} diff --git a/frontend/src/app/_pages/playground/playground.component.css b/frontend/src/app/_pages/playground/playground.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/playground/playground.component.css diff --git a/frontend/src/app/_pages/playground/playground.component.html b/frontend/src/app/_pages/playground/playground.component.html new file mode 100644 index 00000000..1dd7e331 --- /dev/null +++ b/frontend/src/app/_pages/playground/playground.component.html @@ -0,0 +1,18 @@ +<div class="position-fixed d-flex flex-col align-items-center justify-content-center" style="top: 50%; left: 50%; transform: translateX(-50%);"> + <div class="d-flex flex-row align-items-center justify-content-center mt-5"> + <h2 class="text-light my-2"> + Broj tačaka: + </h2> + <mat-slider class="mx-3" [(ngModel)]="backgroundFill" min="0" max="1" step="0.01" (input)="updateFillPref($event)"> + </mat-slider> + + </div> + <div class="d-flex flex-row align-items-center justify-content-center mt-5"> + <h2 class="text-light my-2"> + Animacija: </h2> + <mat-slide-toggle class="mx-3" [(ngModel)]="animateBackground" (change)="updateAnimPref()"></mat-slide-toggle> + + </div> +</div> +<div style="height: 5000px;"> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-model/item-model.component.spec.ts b/frontend/src/app/_pages/playground/playground.component.spec.ts index f696a160..bf66b27e 100644 --- a/frontend/src/app/_elements/item-model/item-model.component.spec.ts +++ b/frontend/src/app/_pages/playground/playground.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ItemModelComponent } from './item-model.component'; +import { PlaygroundComponent } from './playground.component'; -describe('ItemModelComponent', () => { - let component: ItemModelComponent; - let fixture: ComponentFixture<ItemModelComponent>; +describe('PlaygroundComponent', () => { + let component: PlaygroundComponent; + let fixture: ComponentFixture<PlaygroundComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ItemModelComponent ] + declarations: [ PlaygroundComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(ItemModelComponent); + fixture = TestBed.createComponent(PlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/_pages/playground/playground.component.ts b/frontend/src/app/_pages/playground/playground.component.ts new file mode 100644 index 00000000..831132a4 --- /dev/null +++ b/frontend/src/app/_pages/playground/playground.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from '@angular/core'; +import { MatSliderChange } from '@angular/material/slider'; +import { CookieService } from 'ngx-cookie-service'; + +@Component({ + selector: 'app-playground', + templateUrl: './playground.component.html', + styleUrls: ['./playground.component.css'] +}) +export class PlaygroundComponent implements OnInit { + + animateBackground = true; + backgroundFill = 1.0; + + constructor(private cookie: CookieService) { } + + updateFillPref(event: MatSliderChange) { + this.backgroundFill = event.value!; + this.cookie.set('backgroundFill', "" + this.backgroundFill); + } + + updateAnimPref() { + this.cookie.set('animateBackground', "" + this.animateBackground); + } + + ngOnInit(): void { + if (this.cookie.check('animateBackground')) { + this.animateBackground = this.cookie.get('animateBackground') == 'true'; + } + if (this.cookie.check('backgroundFill')) { + this.backgroundFill = parseFloat(this.cookie.get('backgroundFill')); + } + } + +} diff --git a/frontend/src/app/_pages/predict/predict.component.css b/frontend/src/app/_pages/predict/predict.component.css deleted file mode 100644 index dab059a5..00000000 --- a/frontend/src/app/_pages/predict/predict.component.css +++ /dev/null @@ -1,3 +0,0 @@ -#wrapper { - color: #003459; -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/predict/predict.component.html b/frontend/src/app/_pages/predict/predict.component.html deleted file mode 100644 index 13afa8e4..00000000 --- a/frontend/src/app/_pages/predict/predict.component.html +++ /dev/null @@ -1,73 +0,0 @@ - -<div id="wrapper"> - <br> - <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - - <div id="header"> - <h1>Iskoristite prediktor</h1> - </div> - - <br> - - <div class="form-group row mt-3 mb-2 d-flex justify-content-left"> - <!--justify-content-center--> - <h2> Izabrani prediktor: </h2> - <div class="col-10"> - <label for="output" class="col-sm-5 col-form-label">Naziv prediktora: <b>{{predictor.name}}</b></label> - </div> - <div> - <label for="output" class="col-sm-5 col-form-label">Opis prediktora: <b>{{predictor.description}}</b></label> - </div> - - - </div> - <br> - <label for="type" class="form-check-label" ><b>Informacije o prediktoru</b></label> - <div class="col-5 mt-2"> - <label for="type" class="form-check-label" >Prediktor {{predictor.isPublic?"je":"nije"}} javni.</label> - </div> - <div class="col-5 mt-2"> - <label for="type" class="form-check-label" >Prediktor {{predictor.accessibleByLink?"je":"nije"}} dostupan za deljenje.</label> - </div> - <br> - <div class="col-2"> - <label for="dateCreated" class="col-form-label">Datum:</label> - <input type="text" class="form-control-plaintext" id="dateCreated" placeholder="--/--/--" - value="{{predictor.dateCreated | date: 'dd/MM/yyyy'}}" readonly> - </div> - - - <br> - <div > - <!--input --> - <h3>Popunite ulazne kolone:</h3> - <div id="divInputs" class="form-check mt-2"> - <div *ngIf="predictor" class="form-group row mt-3 mb-2 d-flex justify-content-left"> - <div *ngFor="let input of predictor.inputs; let i = index"> - <label for="{{input}}" class="col-sm-2 col-form-label"><b>{{input}}</b></label> - <input name="{{input}}" type="text" [(ngModel)]="inputs[i].value" > - - </div> - - </div> - </div> - - <br> - - </div> - <div> - <label for="output" class="col-sm-2 col-form-label">Izlaz: <b>{{predictor.output}}</b></label> - </div> - - <div class="form-group row mt-5 mb-3"> - <div class="col"></div> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" - (click)="usePredictor();">Iskoristi prediktor</button> - <div class="col"></div> - - </div> - - - - </div> -</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/predict/predict.component.ts b/frontend/src/app/_pages/predict/predict.component.ts deleted file mode 100644 index 39dec0ae..00000000 --- a/frontend/src/app/_pages/predict/predict.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import Predictor from 'src/app/_data/Predictor'; -import { PredictorsService } from 'src/app/_services/predictors.service'; -import shared from 'src/app/Shared'; - -@Component({ - selector: 'app-predict', - templateUrl: './predict.component.html', - styleUrls: ['./predict.component.css'] -}) -export class PredictComponent implements OnInit { - - inputs : Column[] = []; - - - predictor:Predictor; - constructor(private predictS : PredictorsService, private route: ActivatedRoute) { - this.predictor = new Predictor(); - } - - ngOnInit(): void { - this.route.params.subscribe(url => { - this.predictS.getPredictor(url["id"]).subscribe(p => { - - this.predictor = p; - this.predictor.inputs.forEach((p,index)=> this.inputs[index] = new Column(p, "")); - }) - }); - } - - usePredictor(): void{ - this.predictS.usePredictor(this.predictor, this.inputs).subscribe(p => { - shared.openDialog("Obaveštenje", "Prediktor je uspešno poslat na probu."); //pisalo je "na treniranje" ?? - }) - } -} - - -export class Column { - constructor( - public name : string, - public value : (number | string)){ - } -}
\ No newline at end of file diff --git a/frontend/src/app/_pages/profile/profile.component.css b/frontend/src/app/_pages/profile/profile.component.css index 5565d105..428870da 100644 --- a/frontend/src/app/_pages/profile/profile.component.css +++ b/frontend/src/app/_pages/profile/profile.component.css @@ -1,44 +1,21 @@ -body{margin-top:20px; -background-color:#f2f6fc; -color:#69707a; +.card{ + background-color: transparent; + color:var(--offwhite) } -.img-account-profile { - height: 10rem; - border: 1px solid lightgray; -} -.rounded-circle { - border-radius: 50% !important; -} -.card .card-header { - font-weight: 500; -} -.card-header:first-child { - border-radius: 0.35rem 0.35rem 0 0; + +.card-header{ + background-color: var(--ns-primary-50); + color:var(--offwhite) } -.card-header { - padding: 1rem 1.35rem; - margin-bottom: 0; - background-color: rgba(33, 40, 50, 0.03); - border-bottom: 1px solid rgba(33, 40, 50, 0.125); +.card-body{ + background-color: var(--ns-bg-dark-50); } -.form-control, .dataTable-input { - display: block; + +mat-form-field{ width: 100%; - padding: 0.875rem 1.125rem; - font-size: 0.875rem; - font-weight: 400; - line-height: 1; - color: #69707a; - background-color: #fff; - background-clip: padding-box; - border: 1px solid #c5ccd6; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border-radius: 0.35rem; - transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } -.selectedPicture { - border: 2px solid darkgray; +.danger-Text{ + color:var(--ns-warn) } + diff --git a/frontend/src/app/_pages/profile/profile.component.html b/frontend/src/app/_pages/profile/profile.component.html index 557d69fd..37df4f14 100644 --- a/frontend/src/app/_pages/profile/profile.component.html +++ b/frontend/src/app/_pages/profile/profile.component.html @@ -27,17 +27,21 @@ <div class="row gx-3 mb-3"> <!-- Form Group (password)--> <div class="col-md-6"> - <label class="small mb-1" for="inputPassword">Važeća lozinka</label> - <input class="form-control" id="inputPassword" name="inputPassword" type="password" [(ngModel)]="this.oldPass" placeholder="Trenutna lozinka"> - <small *ngIf="wrongPassBool" class="form-text text-danger">Neispravna lozinka.</small> - <small *ngIf="wrongOldPassBool" class="form-text text-danger">Pogrešan format.</small> + <small *ngIf="wrongPassBool" class="form-text danger-Text">Neispravna lozinka.</small> + <mat-form-field appearance="fill"> + <mat-label>Važeća lozinka</mat-label> + <input matInput id="inputPassword" name="inputPassword" type="password" placeholder="" [(ngModel)]="this.oldPass"> + </mat-form-field> + <small *ngIf="wrongOldPassBool" class="form-text danger-Text">Pogrešan format.</small> </div> <!-- Form Group (new password)--> <div class="col-md-6"> - <label class="small mb-1" for="inputNewPassword">Nova lozinka</label> - <input class="form-control" id="inputNewPassword" name="inputNewPassword" type="password" [(ngModel)]="this.newPass1" placeholder="Ukucaj novu lozinku"> - <small *ngIf="wrongNewPassBool" class="form-text text-danger">Lozinke se ne podudaraju.</small> - <small *ngIf="wrongNewPass1Bool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Nova lozinka</mat-label> + <input matInput id="inputNewPassword" name="inputNewPassword" type="password" placeholder="" [(ngModel)]="this.newPass1"> + </mat-form-field> + <small *ngIf="wrongNewPassBool" class="form-text danger-Text">Lozinke se ne podudaraju.</small> + <small *ngIf="wrongNewPass1Bool" class="form-text danger-Text">Pogrešan format.</small> </div> </div> @@ -46,15 +50,17 @@ <div class="col-md-6"> <div class="col text-center"> <!-- Save changes button--> - <button class="btn btn-primary text-center mt-4" type="button" (click)="savePasswordChanges()">Promeni lozinku</button> + <button mat-raised-button color="primary" (click)="savePasswordChanges()">Promeni lozinku</button> </div> </div> <!-- Form Group (new password again)--> <div class="col-md-6"> - <label class="small mb-1" for="inputNewPasswordAgain">Ponovo nova lozinka</label> - <input class="form-control" id="inputNewPasswordAgain" name="inputNewPasswordAgain" type="password" [(ngModel)]="this.newPass2" placeholder="Ukucaj novu lozinku"> - <small *ngIf="wrongNewPassBool" class="form-text text-danger">Lozinke se ne podudaraju.</small> - <small *ngIf="wrongNewPass2Bool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Ponovo nova lozinka</mat-label> + <input matInput id="inputNewPasswordAgain" name="inputNewPasswordAgain" placeholder="" type="password" [(ngModel)]="this.newPass2"> + </mat-form-field> + <small *ngIf="wrongNewPassBool" class="form-text danger-Text">Lozinke se ne podudaraju.</small> + <small *ngIf="wrongNewPass2Bool" class="form-text danger-Text">Pogrešan format.</small> </div> </div> </div> @@ -74,15 +80,19 @@ <div class="row gx-3 mb-3"> <!-- Form Group (username)--> <div class="col-md-6"> - <label class="small mb-1" for="inputUsername">Korisničko ime (kako će ostali korisnici videti tvoje ime)</label> - <input class="form-control" id="inputUsername" name="inputUsername" type="text" [(ngModel)]="this.username"> - <small *ngIf="wrongUsernameBool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Korisničko ime (kako će ostali korisnici videti tvoje ime)</mat-label> + <input matInput id="inputUsername" name="inputUsername" type="text" [(ngModel)]="this.username"> + </mat-form-field> + <small *ngIf="wrongUsernameBool" class="form-text danger-Text">Pogrešan format.</small> </div> <!-- Form Group (email address)--> <div class="col-md-6"> - <label class="small mb-1" for="inputEmailAddress">Email adresa</label> - <input class="form-control" id="inputEmailAddress" name="inputEmailAddress" type="email" [(ngModel)]="this.email"> - <small *ngIf="wrongEmailBool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Email adresa</mat-label> + <input matInput id="inputEmailAddress" name="inputEmailAddress" type="email" [(ngModel)]="this.email"> + </mat-form-field> + <small *ngIf="wrongEmailBool" class="form-text danger-Text">Pogrešan format.</small> </div> </div> @@ -90,15 +100,19 @@ <div class="row gx-3 mb-3"> <!-- Form Group (first name)--> <div class="col-md-6"> - <label class="small mb-1" for="inputFirstName">Ime</label> - <input class="form-control" id="inputFirstName" name="inputFirstName" type="text" [(ngModel)]="this.firstName"> - <small *ngIf="wrongFirstNameBool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Ime</mat-label> + <input matInput id="inputFirstName" name="inputFirstName" type="text" [(ngModel)]="this.firstName"> + </mat-form-field> + <small *ngIf="wrongFirstNameBool" class="form-text danger-Text">Pogrešan format.</small> </div> <!-- Form Group (last name)--> <div class="col-md-6"> - <label class="small mb-1" for="inputLastName">Prezime</label> - <input class="form-control" id="inputLastName" name="inputLastName" type="text" [(ngModel)]="this.lastName"> - <small *ngIf="wrongLastNameBool" class="form-text text-danger">Pogrešan format.</small> + <mat-form-field appearance="fill"> + <mat-label>Prezime</mat-label> + <input matInput id="inputLastName" name="inputLastName" type="text" [(ngModel)]="this.lastName"> + </mat-form-field> + <small *ngIf="wrongLastNameBool" class="form-text danger-Text">Pogrešan format.</small> </div> </div> @@ -121,7 +135,7 @@ <div class="row mt-5"> <div class="col text-center"> <!-- Save changes button--> - <button class="btn btn-primary text-center" type="button" (click)="saveInfoChanges()">Sačuvaj izmene</button> + <button mat-raised-button color="primary" (click)="saveInfoChanges()" >Sačuvaj izmene</button> </div> </div> </form> 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..94679055 --- /dev/null +++ b/frontend/src/app/_pages/test/test.component.html @@ -0,0 +1,5 @@ +<app-pie-chart></app-pie-chart> +<app-doughnut-chart></app-doughnut-chart> +<app-barchart></app-barchart> +<app-box-plot></app-box-plot> +<app-heatmap></app-heatmap>
\ 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/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts index 9e3f9f2f..cc5ad688 100644 --- a/frontend/src/app/_services/auth.service.ts +++ b/frontend/src/app/_services/auth.service.ts @@ -3,7 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { JwtHelperService } from '@auth0/angular-jwt'; import { CookieService } from 'ngx-cookie-service'; import shared from '../Shared'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; const jwtHelper = new JwtHelperService(); @@ -12,9 +12,8 @@ const jwtHelper = new JwtHelperService(); }) export class AuthService { - public loggedInEvent: EventEmitter<boolean> = new EventEmitter(); - shared = shared; + public loggedInEvent: EventEmitter<boolean> = new EventEmitter(); constructor(private http: HttpClient, private cookie: CookieService) { } @@ -23,7 +22,7 @@ export class AuthService { } register(user: any) { - return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user }, { responseType: 'text' }); + return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user },{ headers: this.authHeader() , responseType: 'text' }); } getGuestToken() { @@ -53,26 +52,19 @@ export class AuthService { } var property = jwtHelper.decodeToken(this.cookie.get('token')); var username = property['name']; - if (username != "") { this.refresher = setTimeout(() => { this.http.post(`${Configuration.settings.apiURL}/auth/renewJwt`, {}, { headers: this.authHeader(), responseType: 'text' }).subscribe((response) => { this.authenticate(response); }); }, exp.getTime() - new Date().getTime() - 60000); - } - else { - this.refresher = setTimeout(() => { - this.getGuestToken().subscribe((response) => { - this.authenticate(response); - }); - }, exp.getTime() - new Date().getTime() - 60000); - } + } addGuestToken() { this.getGuestToken().subscribe((token) => { this.authenticate(token); + location.reload(); }); } @@ -83,7 +75,6 @@ export class AuthService { } this.cookie.set('token', token, exp); this.updateUser(); - this.loggedInEvent.emit(true); } updateUser() { @@ -108,4 +99,15 @@ export class AuthService { authHeader() { return new HttpHeaders().set("Authorization", "Bearer " + this.cookie.get('token')); } + alreadyGuest(){ + if(this.cookie.check('token')){ + const token = this.cookie.get('token'); + const decodedToken = jwtHelper.decodeToken(token); + if(decodedToken.role=="Guest") + return true; + } + return false; + } + + } diff --git a/frontend/src/app/configuration.service.spec.ts b/frontend/src/app/_services/configuration.service.spec.ts index ec51dfa5..4b9322b5 100644 --- a/frontend/src/app/configuration.service.spec.ts +++ b/frontend/src/app/_services/configuration.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; -import { ConfigurationService } from './configuration.service'; +import { Configuration as ConfigurationService } from './configuration.service'; describe('ConfigurationService', () => { let service: ConfigurationService; diff --git a/frontend/src/app/configuration.service.ts b/frontend/src/app/_services/configuration.service.ts index 4d2b0987..cda1091a 100644 --- a/frontend/src/app/configuration.service.ts +++ b/frontend/src/app/_services/configuration.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { IConfig } from '../app/_data/IConfig' +import { IConfig } from '../_data/IConfig' @Injectable() export class Configuration { diff --git a/frontend/src/app/_services/csv-parse.service.ts b/frontend/src/app/_services/csv-parse.service.ts index 4a05535a..aae10193 100644 --- a/frontend/src/app/_services/csv-parse.service.ts +++ b/frontend/src/app/_services/csv-parse.service.ts @@ -1,4 +1,10 @@ import { Injectable } from "@angular/core"; +import * as FileSaver from 'file-saver'; +import * as XLSX from 'xlsx'; + +const EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; +const EXCEL_EXTENSION = '.xlsx'; + @Injectable({ providedIn: 'root' }) export class CsvParseService { @@ -47,10 +53,42 @@ export class CsvParseService { if (strMatchedValue.length > 0) arrData[arrData.length - 1].push(strMatchedValue); - else + else arrData[arrData.length - 1].push(null); } return (arrData); } + + ConvertJSONToCSV(objArray: string, headerList: { [x: string]: any; }) { + let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; + let str = ''; + let row = 'S.No,'; + for (let index in headerList) { + row += headerList[index] + ','; + } + row = row.slice(0, -1); + str += row + '\r\n'; + for (let i = 0; i < array.length; i++) { + let line = (i + 1) + ''; + for (let index in headerList) { + let head = headerList[index]; + line += ',' + array[i][head]; + } + str += line + '\r\n'; + } + return str; + } + + public exportAsExcelFile(json: any[], excelFileName: string): void { + const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(json); + const workbook: XLSX.WorkBook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] }; + const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); + this.saveAsExcelFile(excelBuffer, excelFileName); + } + + private saveAsExcelFile(buffer: any, fileName: string): void { + const data: Blob = new Blob([buffer], { type: EXCEL_TYPE }); + FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION); + } }
\ No newline at end of file diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index b2272f0a..2211996f 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; import Dataset from '../_data/Dataset'; import { AuthService } from './auth.service'; @@ -25,7 +25,10 @@ export class DatasetsService { } getDatasetFile(fileId: any): any { - return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/true/${fileId}`, { headers: this.authService.authHeader(), responseType: 'text' }); + return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/-1/11`, { headers: this.authService.authHeader(), responseType: 'text' }); + } + getDatasetFilePartial(fileId: any, startRow: number, rowNum: number): Observable<any> { + return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${startRow}/${rowNum}`, { headers: this.authService.authHeader(), responseType: 'text' }); } editDataset(dataset: Dataset): Observable<Dataset> { diff --git a/frontend/src/app/_services/experiments.service.ts b/frontend/src/app/_services/experiments.service.ts index 0d0d372b..29569fca 100644 --- a/frontend/src/app/_services/experiments.service.ts +++ b/frontend/src/app/_services/experiments.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; import Experiment from '../_data/Experiment'; import { AuthService } from './auth.service'; @@ -19,4 +19,8 @@ export class ExperimentsService { getMyExperiments(): Observable<Experiment[]> { return this.http.get<Experiment[]>(`${Configuration.settings.apiURL}/experiment/getmyexperiments`, { headers: this.authService.authHeader() }); } + + updateExperiment(experiment: Experiment): Observable<Experiment> { + return this.http.put<Experiment>(`${Configuration.settings.apiURL}/experiment/` + experiment._id, experiment, { headers: this.authService.authHeader() }); + } } diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts index 1130b12c..fc888556 100644 --- a/frontend/src/app/_services/models.service.ts +++ b/frontend/src/app/_services/models.service.ts @@ -1,10 +1,10 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import Model, { ProblemType } from '../_data/Model'; +import Model from '../_data/Model'; import { AuthService } from './auth.service'; import { Observable } from 'rxjs'; import Dataset from '../_data/Dataset'; -import { Configuration } from '../configuration.service'; +import { Configuration } from '../_services/configuration.service'; @Injectable({ providedIn: 'root' @@ -31,16 +31,20 @@ export class ModelsService { addModel(model: Model): Observable<any> { return this.http.post(`${Configuration.settings.apiURL}/model/add`, model, { headers: this.authService.authHeader() }); } + addDataset(dataset: Dataset): Observable<any> { + return this.http.post(`${Configuration.settings.apiURL}/dataset/add`, dataset, { headers: this.authService.authHeader() }); + } trainModel(modelId: string, experimentId: string): Observable<any> { return this.http.post(`${Configuration.settings.apiURL}/model/trainmodel`, { ModelId: modelId, ExperimentId: experimentId }, { headers: this.authService.authHeader(), responseType: 'text' }); } + getMyDatasets(): Observable<Dataset[]> { + return this.http.get<Dataset[]>(`${Configuration.settings.apiURL}/dataset/mydatasets`, { headers: this.authService.authHeader() }); + } + getMyModels(): Observable<Model[]> { return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/mymodels`, { headers: this.authService.authHeader() }); } - getMyModelsByType(problemType: ProblemType): Observable<Model[]> { - return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/mymodelsbytype/` + problemType, { headers: this.authService.authHeader() }); - } editModel(model: Model): Observable<Model> { return this.http.put<Model>(`${Configuration.settings.apiURL}/model/`, model, { headers: this.authService.authHeader() }); @@ -49,5 +53,9 @@ export class ModelsService { deleteModel(model: Model) { return this.http.delete(`${Configuration.settings.apiURL}/model/` + model.name, { headers: this.authService.authHeader(), responseType: "text" }); } - + + getPublicModels(): Observable<Model[]> { + return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/publicmodels`, { headers: this.authService.authHeader() }); + } + } diff --git a/frontend/src/app/_services/predictors.service.ts b/frontend/src/app/_services/predictors.service.ts index a24ee708..e2033e3e 100644 --- a/frontend/src/app/_services/predictors.service.ts +++ b/frontend/src/app/_services/predictors.service.ts @@ -1,9 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; import Predictor from '../_data/Predictor'; -import { Column } from '../_pages/predict/predict.component'; import { AuthService } from './auth.service'; @Injectable({ @@ -31,3 +30,10 @@ export class PredictorsService { return this.http.get<Predictor[]>(`${Configuration.settings.apiURL}/predictor/mypredictors`, { headers: this.authService.authHeader() }); } } + +export class Column { + constructor( + public name: string, + public value: (number | string)) { + } +}
\ No newline at end of file diff --git a/frontend/src/app/_services/signal-r.service.ts b/frontend/src/app/_services/signal-r.service.ts index 6a2e61e9..234636fe 100644 --- a/frontend/src/app/_services/signal-r.service.ts +++ b/frontend/src/app/_services/signal-r.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import * as signalR from "@microsoft/signalr"; import { CookieService } from 'ngx-cookie-service'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; @Injectable({ providedIn: 'root' }) @@ -16,18 +16,13 @@ export class SignalRService { }).build(); this.hubConnection.on("Notify", (message: string) => { - //console.log(" " + message); + console.log(" " + message); }); this.hubConnection .start() - .then(() => {}) - .catch(err => {}) + .then(() => console.log("con Started")) + .catch(err => console.log("Error" + err)) } - - public stopConnection = () => { - this.hubConnection?.stop(); - } - constructor(private cookie: CookieService) { } } diff --git a/frontend/src/app/_services/user-info.service.ts b/frontend/src/app/_services/user-info.service.ts index 5d3394f6..8efeb903 100644 --- a/frontend/src/app/_services/user-info.service.ts +++ b/frontend/src/app/_services/user-info.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { Configuration } from '../configuration.service'; +import { Configuration } from './configuration.service'; import User from '../_data/User'; import { AuthService } from './auth.service'; diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index a0952d21..f5f1ccae 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -3,33 +3,21 @@ import { RouterModule, Routes } from '@angular/router'; import { AuthGuardService } from './_services/auth-guard.service'; import { HomeComponent } from './_pages/home/home.component'; -import { MyDatasetsComponent } from './_pages/my-datasets/my-datasets.component'; -import { MyModelsComponent } from './_pages/my-models/my-models.component'; -import { MyPredictorsComponent } from './_pages/my-predictors/my-predictors.component'; -import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-predictors.component'; -import { BrowseDatasetsComponent } from './_pages/browse-datasets/browse-datasets.component'; -import { SettingsComponent } from './_pages/settings/settings.component'; import { ProfileComponent } from './_pages/profile/profile.component'; -import { PredictComponent } from './_pages/predict/predict.component'; -import { FilterDatasetsComponent } from './_pages/filter-datasets/filter-datasets.component'; -import { ExperimentComponent } from './experiment/experiment.component'; -import { TrainingComponent } from './training/training.component'; +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' } }, - /*{ path: 'add-model', component: AddModelComponent, data: { title: 'Dodaj model' } },*/ - { path: 'experiment', component: ExperimentComponent, data: { title: 'Dodaj eksperiment' } }, - { path: 'training/:id', component: TrainingComponent, data: { title: 'Treniraj model' } }, - { path: 'training', component: TrainingComponent, data: { title: 'Treniraj model' } }, - { path: 'my-datasets', component: MyDatasetsComponent, canActivate: [AuthGuardService], data: { title: 'Moji izvori podataka' } }, - { path: 'my-models', component: MyModelsComponent, canActivate: [AuthGuardService], data: { title: 'Moji modeli' } }, - { path: 'my-predictors', component: MyPredictorsComponent, canActivate: [AuthGuardService], data: { title: 'Moji trenirani modeli' } }, - { path: 'settings', component: SettingsComponent, canActivate: [AuthGuardService], data: { title: 'Podešavanja' } }, + { path: 'experiment', component: ExperimentComponent, data: { title: 'Eksperiment' } }, + { path: 'archive', component: ArchiveComponent, data: { title: 'Arhiva' } }, { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } }, - { path: 'browse-datasets', component: FilterDatasetsComponent, data: { title: 'Javni izvori podataka' } }, - { path: 'browse-predictors', component: BrowsePredictorsComponent, data: { title: 'Javni trenirani modeli' } }, - { path: 'predict/:id', component: PredictComponent, data: { title: 'Predvidi vrednosti' } }, - + { path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } }, + { 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 daf93cc1..d15793e7 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -1,10 +1,13 @@ -<app-reactive-background [bgColor]="'#003459'" [lineColor]="'#00a8e8'" [pointColor]="'#cfeffb'" - [cursorLineColor]="'#888888'" [numPoints]="300"> +<app-gradient-background colorHorizontal1="rgba(0, 8, 45, 0.5)" colorHorizontal2="rgba(0, 52, 89, 0.5)" colorVertical1="rgba(0, 52, 89, 0.5)" colorVertical2="rgba(0, 152, 189, 0.5)"></app-gradient-background> +<app-reactive-background [speed]="0.0005" [scrollSpeed]="0.25" [minDistance]="0.1" [maxSize]="3" [cursorDistance]="0.17" lineColor="#00a8e8" pointColor="rgba(0, 188, 252, 0.33)" cursorLineColor="#00a8e8" [numPoints]="300"> +</app-reactive-background> +<app-reactive-background [speed]="0.0008" [scrollSpeed]="0.5" [minDistance]="0.12" [maxSize]="4" [cursorDistance]="0.19" lineColor="#00a8e8" pointColor="rgba(0, 188, 252, 0.66)" cursorLineColor="#00a8e8" [numPoints]="200"> +</app-reactive-background> +<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> -<div class="container h-100"> - <router-outlet></router-outlet> - <!--<app-barchart></app-barchart> - <app-scatterchart></app-scatterchart>--> -</div> +<a class="bg-controls" style="z-index: 1000;" routerLink="playground"> + <mat-icon color="accent">settings_suggest</mat-icon> +</a> +<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 54c18bec..e301b46f 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,9 +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) { - constructor(private router: Router, private titleService: Title,private authService:AuthService,private signalRService:SignalRService,private http:HttpClient) { } + } + ngAfterViewInit(): void { + } ngOnInit() { this.router.events @@ -36,22 +39,11 @@ export class AppComponent implements OnInit { this.titleService.setTitle(`${title} - Igrannonica`); } }); - if(!this.authService.isAuthenticated()) - { + if (!this.authService.isAuthenticated()) { + if(!this.authService.alreadyGuest()) this.authService.addGuestToken(); - } - this.signalRService.startConnection(); - //this.startHttpRequest(); - - - - - } - private startHttpRequest = () => { - this.http.get('http://localhost:5283/chatHub') - .subscribe(res => { - }) + } + this.signalRService.startConnection(); + //this.startHttpRequest(); } - - } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 51374bd4..d44bf6ad 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -8,97 +8,90 @@ import { MatIconModule } from '@angular/material/icon'; import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown'; import { NgChartsModule } from 'ng2-charts'; import { Ng2SearchPipeModule } from 'ng2-search-filter'; -import { AppComponent } from './app.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { DatasetLoadComponent } from './_elements/dataset-load/dataset-load.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { LoginModalComponent } from './_modals/login-modal/login-modal.component'; import { ReactiveFormsModule } from '@angular/forms'; -import { RegisterModalComponent } from './_modals/register-modal/register-modal.component'; - +import { AppComponent } from './app.component'; +// Modules and modals +import { Configuration } from './_services/configuration.service'; import { MaterialModule } from './material.module'; +import { LoginModalComponent } from './_modals/login-modal/login-modal.component'; +import { RegisterModalComponent } from './_modals/register-modal/register-modal.component'; +import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; +import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.component'; +import { EncodingDialogComponent } from './_modals/encoding-dialog/encoding-dialog.component'; +import { MissingvaluesDialogComponent } from './_modals/missingvalues-dialog/missingvalues-dialog.component'; +import { SaveExperimentDialogComponent } from './_modals/save-experiment-dialog/save-experiment-dialog.component'; +// Pages import { HomeComponent } from './_pages/home/home.component'; -import { NavbarComponent } from './_elements/navbar/navbar.component'; -import { ItemPredictorComponent } from './_elements/item-predictor/item-predictor.component'; -import { ItemDatasetComponent } from './_elements/item-dataset/item-dataset.component'; -import { CarouselComponent } from './_elements/carousel/carousel.component'; -import { SettingsComponent } from './_pages/settings/settings.component'; import { ProfileComponent } from './_pages/profile/profile.component'; -import { MyPredictorsComponent } from './_pages/my-predictors/my-predictors.component'; -import { MyDatasetsComponent } from './_pages/my-datasets/my-datasets.component'; -import { MyModelsComponent } from './_pages/my-models/my-models.component'; -import { BrowseDatasetsComponent } from './_pages/browse-datasets/browse-datasets.component'; -import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-predictors.component'; -import { PredictComponent } from './_pages/predict/predict.component'; -import { ScatterchartComponent } from './scatterchart/scatterchart.component'; -import { BarchartComponent } from './barchart/barchart.component'; +import { ExperimentComponent } from './_pages/experiment/experiment.component'; +import { PlaygroundComponent } from './_pages/playground/playground.component'; +import { ArchiveComponent } from './_pages/archive/archive.component'; +// Charts +import { ScatterchartComponent } from './_elements/_charts/scatterchart/scatterchart.component'; +import { BarchartComponent } from './_elements/_charts/barchart/barchart.component'; +import { PieChartComponent } from './_elements/_charts/pie-chart/pie-chart.component'; +import { BoxPlotComponent } from './_elements/_charts/box-plot/box-plot.component'; +import {LineChartComponent} from './_elements/_charts/line-chart/line-chart.component'; +// Elements +import { NavbarComponent } from './_elements/navbar/navbar.component'; import { NotificationsComponent } from './_elements/notifications/notifications.component'; import { DatatableComponent } from './_elements/datatable/datatable.component'; -import { FilterDatasetsComponent } from './_pages/filter-datasets/filter-datasets.component'; import { ReactiveBackgroundComponent } from './_elements/reactive-background/reactive-background.component'; -import { ItemModelComponent } from './_elements/item-model/item-model.component'; -import { AnnvisualComponent } from './_elements/annvisual/annvisual.component'; -import { ExperimentComponent } from './experiment/experiment.component'; import { LoadingComponent } from './_elements/loading/loading.component'; -import { ModelLoadComponent } from './_elements/model-load/model-load.component'; -import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; -import { AddNewDatasetComponent } from './_elements/add-new-dataset/add-new-dataset.component'; import { GraphComponent } from './_elements/graph/graph.component'; -import { TrainingComponent } from './training/training.component'; -import { ItemExperimentComponent } from './_elements/item-experiment/item-experiment.component'; -import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.component'; -import { Configuration } from './configuration.service'; -import { PointLinechartComponent } from './point-linechart/point-linechart.component'; -import { MixedChartComponent } from './mixed-chart/mixed-chart.component'; - -import { LineChartComponent } from './_elements/line-chart/line-chart.component'; +import { GradientBackgroundComponent } from './_elements/gradient-background/gradient-background.component'; +import { PlaylistComponent } from './_elements/playlist/playlist.component'; +import { FormDatasetComponent } from './_elements/form-dataset/form-dataset.component'; +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'; +import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component'; +import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; + export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); } @NgModule({ declarations: [ AppComponent, - DatasetLoadComponent, LoginModalComponent, RegisterModalComponent, HomeComponent, NavbarComponent, - ItemPredictorComponent, - ItemDatasetComponent, - CarouselComponent, - SettingsComponent, ProfileComponent, - MyPredictorsComponent, - MyDatasetsComponent, - MyModelsComponent, - BrowseDatasetsComponent, - BrowsePredictorsComponent, - PredictComponent, ScatterchartComponent, BarchartComponent, NotificationsComponent, DatatableComponent, - FilterDatasetsComponent, ReactiveBackgroundComponent, - ItemModelComponent, - AnnvisualComponent, ExperimentComponent, LoadingComponent, - ModelLoadComponent, AlertDialogComponent, - AddNewDatasetComponent, GraphComponent, - TrainingComponent, - ItemExperimentComponent, YesNoDialogComponent, - LineChartComponent, - PointLinechartComponent, - MixedChartComponent, - LineChartComponent, + PlaygroundComponent, + GradientBackgroundComponent, + PlaylistComponent, + ArchiveComponent, + FormDatasetComponent, + FormModelComponent, + ColumnTableComponent, + PieChartComponent, + BoxPlotComponent, + FolderComponent, + EncodingDialogComponent, + MissingvaluesDialogComponent, + TestComponent, + DoughnutChartComponent, + HeatmapComponent, MetricViewComponent, - - + LineChartComponent, + SaveExperimentDialogComponent ], imports: [ BrowserModule, @@ -113,6 +106,7 @@ export function initializeApp(appConfig: Configuration) { MatIconModule, NgChartsModule, Ng2SearchPipeModule, + HeatMapAllModule ], providers: [ Configuration, diff --git a/frontend/src/app/experiment/experiment.component.css b/frontend/src/app/experiment/experiment.component.css deleted file mode 100644 index d84a897e..00000000 --- a/frontend/src/app/experiment/experiment.component.css +++ /dev/null @@ -1,48 +0,0 @@ -#header { - background-color: #003459; - padding-top: 30px; - padding-bottom: 20px; -} - -#header h1 { - font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; - text-align: center; - color: white; -} - -#container { - border-radius: 8px; -} - -#wrapper { - color: #003459; -} - -.btnType1 { - background-color: #003459; - color: white; -} - -.btnType2 { - background-color: white; - color: #003459; - border-color: #003459; -} - -.selectedDatasetClass { - /*border-color: 2px solid #003459;*/ - background-color: lightblue; -} - -ul li:hover { - background-color: lightblue; -} - -h2 { - color: #003459; -} - -.boldClass { - font-weight: bold; - font-size: 125%; -}
\ No newline at end of file diff --git a/frontend/src/app/experiment/experiment.component.html b/frontend/src/app/experiment/experiment.component.html index 4bdd79b2..62236cce 100644 --- a/frontend/src/app/experiment/experiment.component.html +++ b/frontend/src/app/experiment/experiment.component.html @@ -4,15 +4,15 @@ <div id="wrapper"> <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - <div class="d-flex flex-row justify-content-center align-items-center my-3 links"> - <a id="firstStep" href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="0" (click)="updateCarouselIndex(0);" [ngClass]="{'boldClass' : carouselIndex == 0}" style="text-decoration: none">Izvor podataka</a> + <div class="d-flex flex-row justify-content-center align-items-center my-3"> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="0">Izvor podataka</a> <mat-icon>arrow_forward</mat-icon> - <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1" (click)="updateCarouselIndex(1)" [ngClass]="{'boldClass' : carouselIndex == 1}" style="text-decoration: none">Preprocesiranje</a> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1">Preprocesiranje</a> <mat-icon>arrow_forward</mat-icon> - <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2" (click)="updateCarouselIndex(2)" [ngClass]="{'boldClass' : carouselIndex == 2}" style="text-decoration: none">Dodaj eksperiment</a> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2">Dodaj eksperiment</a> </div> - <div id="carouselExampleControls" class="carousel px-5 mt-5" data-bs-wrap="false" data-bs-ride="carousel" data-bs-interval="false"> + <div id="carouselExampleControls" class="carousel slide px-5 mt-5" data-bs-wrap="false" data-bs-ride="carousel" data-bs-interval="false"> <div class="carousel-inner"> <div class="carousel-item active mt-2"> <h2 class="mb-5">1. Izvor podataka</h2> @@ -63,10 +63,8 @@ <h3 class="mt-5">Popunjavanje nedostajućih vrednosti:</h3> <div class="form-check" *ngIf="selectedDataset"> <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteRows " class="form-check-input" value="deleteRows" name="fillMissing " id="delRows" checked data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> - <label *ngIf="selectedColumnsInfoArray.length == selectedDataset.columnInfo.length" for="delRows" class="form-check-label ">Obriši sve - redove sa nedostajućim vrednostima ({{selectedDataset.nullRows}} od {{selectedDataset.rowCount}})</label> - <label *ngIf="selectedColumnsInfoArray.length != selectedDataset.columnInfo.length" for="delRows" class="form-check-label ">Obriši sve - redove sa nedostajućim vrednostima</label><br> + <label for="delRows" class="form-check-label ">Obriši sve + redove sa nedostajućim vrednostima ({{selectedDataset.nullRows}} od {{selectedDataset.rowCount}})</label><br> <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteColumns " class="form-check-input" value="deleteCols" name="fillMissing " id="delCols" data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> <label for="delCols" class="form-check-label ">Obriši sve kolone sa nedostajućim vrednostima ({{countSelectedNullCols()}} od {{selectedDataset.columnInfo.length}})</label><br> @@ -123,17 +121,13 @@ </div> <div class="flex-shrink-1 mx-3"> - <div class="input-group" *ngIf="column.columnName != this.experiment.outputColumn"> + <div class="input-group"> <label class="form-control" [for]="'delCol_'+column.columnName">Izbriši - kolonu - <input type="radio" [id]="'delCol_'+column.columnName" + kolonu + <input type="radio" [id]="'delCol_'+column.columnName" [name]="'delOp_'+column.columnName" - (change)="emptyFillTextInput(column.columnName)"> - </label> + (change)="emptyFillTextInput(column.columnName)"></label> </div> - <label class="form-control text-secondary" role="alert" *ngIf="column.columnName == this.experiment.outputColumn"> - (Izlazna kolona) - </label> </div> <div class="flex-shrink-1 mx-3"> @@ -213,7 +207,7 @@ </div> <div class="carousel-item mt-2"> - <h2 class="mb-4">3. Dodaj eksperiment</h2> + <h2 class="mb-4">3. Dodaj eskperiment</h2> <div class="row"> <div class="col"></div> @@ -248,11 +242,11 @@ </div> - <div class="m-3 d-flex flex-row align-items-center" style=" margin-left: auto;"> - <button class="me-auto" *ngIf="carouselIndex != 0" mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="prev" (click)="updateCarouselIndex(carouselIndex - 1)"> + <div class="m-3 d-flex flex-row justify-content-between align-items-center" style=" margin-left: auto;"> + <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="prev"> <mat-icon>arrow_backward</mat-icon> </button> - <button class="ms-auto" *ngIf="carouselIndex != 2" mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="next" (click)="updateCarouselIndex(carouselIndex + 1)"> + <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="next"> <mat-icon>arrow_forward</mat-icon> </button> </div> diff --git a/frontend/src/app/experiment/experiment.component.ts b/frontend/src/app/experiment/experiment.component.ts index 64adce3f..2d0f6ec5 100644 --- a/frontend/src/app/experiment/experiment.component.ts +++ b/frontend/src/app/experiment/experiment.component.ts @@ -6,9 +6,6 @@ import { ModelsService } from '../_services/models.service'; import Shared from '../Shared'; import { ExperimentsService } from '../_services/experiments.service'; import { ColumnEncoding } from '../_data/Experiment'; -import { Router } from '@angular/router'; -import { TrainingComponent } from '../training/training.component'; -import { NEVER, retryWhen } from 'rxjs'; @Component({ selector: 'app-experiment', @@ -32,9 +29,8 @@ export class ExperimentComponent implements OnInit { selectedNotNullColumnsArray: string[] = []; tempTestSetDistribution = 90; - carouselIndex: number = 0; - constructor(private experimentsService: ExperimentsService, private router: Router) { + constructor(private modelsService: ModelsService, private experimentsService: ExperimentsService) { } ngOnInit(): void { @@ -47,6 +43,7 @@ export class ExperimentComponent implements OnInit { this.experiment.outputColumn = this.selectedDataset.columnInfo[this.selectedDataset.columnInfo.length - 1].columnName; this.resetColumnEncodings(); + console.log(this.experiment.encodings); } resetColumnEncodings() { @@ -169,7 +166,7 @@ export class ExperimentComponent implements OnInit { } saveExperiment() { - if (this.selectedDataset == undefined) { + if (this.selectedDataset == undefined) { Shared.openDialog("Greška", "Izvor podataka nije izabran!"); return; } @@ -195,14 +192,16 @@ export class ExperimentComponent implements OnInit { this.experiment.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10; - //console.log("Eksperiment:", this.experiment); + console.log("Eksperiment:", this.experiment); this.experimentsService.addExperiment(this.experiment).subscribe((response) => { this.experiment = response; - Shared.openYesNoDialog("Obaveštenje", "Eksperiment je uspešno kreiran. Da li želite da pređete na treniranje modela?", () => { - this.router.navigate(['/training', this.experiment._id]); - }); + this.selectedColumnsInfoArray = []; + this.selectedNotNullColumnsArray = []; + this.experiment.encodings = []; + + Shared.openDialog("Obaveštenje", "Eksperiment je uspešno kreiran."); }, (error) => { if (error.error == "Experiment with this name exists") { Shared.openDialog("Greška", "Eksperiment sa unetim nazivom već postoji u Vašoj kolekciji. Unesite neki drugi naziv."); @@ -220,23 +219,4 @@ export class ExperimentComponent implements OnInit { } return counter; } - - updateCarouselIndex(newIndex: number) { - if (newIndex > 2) - newIndex = 2; - else if (newIndex < 0) - newIndex = 0; - - if (this.carouselIndex == 0 && (newIndex == 1 || newIndex == 2)) - this.checkRequiredData(); - this.carouselIndex = newIndex; - } - - checkRequiredData() { - if (this.selectedDataset == undefined) { - (<HTMLAnchorElement>document.getElementById("firstStep")).click(); - Shared.openDialog("Pažnja", "Potrebno je da dodate ili izabere izvor podataka kako biste prešli na naredni korak (preprocesiranje)."); - return; - } - } } diff --git a/frontend/src/app/material.module.ts b/frontend/src/app/material.module.ts index d16cef3d..baaae58b 100644 --- a/frontend/src/app/material.module.ts +++ b/frontend/src/app/material.module.ts @@ -1,21 +1,122 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatButtonModule } from '@angular/material/button'; +// Material Form Controls +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +// Material Navigation +import { MatMenuModule } from '@angular/material/menu'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; +// Material Layout +import { MatCardModule } from '@angular/material/card'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatGridListModule } from '@angular/material/grid-list'; +import { MatListModule } from '@angular/material/list'; +import { MatStepperModule } from '@angular/material/stepper'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatTreeModule } from '@angular/material/tree'; +// Material Buttons & Indicators +import { MatButtonModule } from '@angular/material/button'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatBadgeModule } from '@angular/material/badge'; +import { MatChipsModule } from '@angular/material/chips'; import { MatIconModule } from '@angular/material/icon'; - +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatRippleModule } from '@angular/material/core'; +// Material Popups & Modals +import { MatBottomSheetModule } from '@angular/material/bottom-sheet'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +// Material Data tables +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatTableModule } from '@angular/material/table'; @NgModule({ - exports: [ + declarations: [], + imports: [ CommonModule, - MatDialogModule, - MatButtonModule, + MatAutocompleteModule, + MatCheckboxModule, + MatDatepickerModule, MatFormFieldModule, MatInputModule, + MatRadioModule, + MatSelectModule, + MatSliderModule, + MatSlideToggleModule, + MatMenuModule, + MatSidenavModule, + MatToolbarModule, + MatCardModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatListModule, + MatStepperModule, + MatTabsModule, + MatTreeModule, + MatButtonModule, + MatButtonToggleModule, + MatBadgeModule, + MatChipsModule, + MatIconModule, + MatProgressSpinnerModule, + MatProgressBarModule, + MatRippleModule, + MatBottomSheetModule, + MatDialogModule, + MatSnackBarModule, + MatTooltipModule, + MatPaginatorModule, + MatSortModule, + MatTableModule + ], + exports: [ + MatAutocompleteModule, MatCheckboxModule, - MatIconModule + MatDatepickerModule, + MatFormFieldModule, + MatInputModule, + MatRadioModule, + MatSelectModule, + MatSliderModule, + MatSlideToggleModule, + MatMenuModule, + MatSidenavModule, + MatToolbarModule, + MatCardModule, + MatDividerModule, + MatExpansionModule, + MatGridListModule, + MatListModule, + MatStepperModule, + MatTabsModule, + MatTreeModule, + MatButtonModule, + MatButtonToggleModule, + MatBadgeModule, + MatChipsModule, + MatIconModule, + MatProgressSpinnerModule, + MatProgressBarModule, + MatRippleModule, + MatBottomSheetModule, + MatDialogModule, + MatSnackBarModule, + MatTooltipModule, + MatPaginatorModule, + MatSortModule, + MatTableModule ] }) -export class MaterialModule {}
\ No newline at end of file +export class MaterialModule { }
\ No newline at end of file diff --git a/frontend/src/app/scatterchart/scatterchart.component.css b/frontend/src/app/scatterchart/scatterchart.component.css deleted file mode 100644 index 5735217e..00000000 --- a/frontend/src/app/scatterchart/scatterchart.component.css +++ /dev/null @@ -1,6 +0,0 @@ -#divScatterChart{ - background-color: beige; - display: block; - width: 400px; - height: 200px; -}
\ No newline at end of file diff --git a/frontend/src/app/scatterchart/scatterchart.component.html b/frontend/src/app/scatterchart/scatterchart.component.html deleted file mode 100644 index 2b30fe1f..00000000 --- a/frontend/src/app/scatterchart/scatterchart.component.html +++ /dev/null @@ -1,4 +0,0 @@ -<p>Scatter chart:</p> -<div id="divScatterChart"> - <canvas id="ScatterCharts"> </canvas> -</div>
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.css b/frontend/src/app/training/training.component.css deleted file mode 100644 index 490c56b5..00000000 --- a/frontend/src/app/training/training.component.css +++ /dev/null @@ -1,39 +0,0 @@ -#header { - background-color: #003459; - padding-top: 30px; - padding-bottom: 20px; -} - -#header h1 { - font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; - text-align: center; - color: white; -} - -#container { - border-radius: 8px; -} - -#wrapper { - color: #003459; -} - -.btnType1 { - background-color: #003459; - color: white; -} - -.btnType2 { - background-color: white; - color: #003459; - border-color: #003459; -} - -.selectedExperimentClass { - /*border-color: 2px solid #003459;*/ - background-color: lightblue; -} - -ul li:hover { - background-color: lightblue; -}
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.html b/frontend/src/app/training/training.component.html index deac88cc..672e75fb 100644 --- a/frontend/src/app/training/training.component.html +++ b/frontend/src/app/training/training.component.html @@ -2,40 +2,45 @@ <h1>Trenirajte veštačku neuronsku mrežu</h1> </div> <div id="wrapper" class="mb-4"> - <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - <h2>1. Izaberite eksperiment iz kolekcije</h2> - <div class="px-5 mt-5 mb-3"> - <input type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> - </div> - <div class="overflow-auto px-5" style="max-height: 500px;"> - <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let experiment of myExperiments|filter:term" [ngClass]="{'selectedExperimentClass': this.selectedExperiment == experiment}"> - <app-item-experiment [experiment]="experiment" (click)="selectThisExperiment(experiment);"></app-item-experiment> - </li> - </ul> - </div> + <div id="container" class="container p-5 row" style="background-color: white; min-height: 100%;"> + <div class="col"></div> - <h2 class="mt-5 mb-2">2. Izaberite model</h2> - <app-model-load *ngIf="selectedExperiment" (selectedModelChangeEvent)="selectModel($event)" [forExperiment]="selectedExperiment"></app-model-load> - <h3 *ngIf="!selectedExperiment">Morate prvo izabrati eksperiment.</h3> + <div class="col-10"> - <h2 class="my-5">3. Treniranje modela</h2> + <h2>1. Izaberite eksperiment iz kolekcije</h2> + <div class="px-5 mt-5 mb-3"> + <input type="text" class="form-control" placeholder="Pretraga" + [(ngModel)]="term"> + </div> + <div class="overflow-auto px-5" style="max-height: 500px;"> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let experiment of myExperiments|filter:term" + [ngClass]="{'selectedExperimentClass': this.selectedExperiment == experiment}"> + <app-item-experiment [experiment]="experiment" + (click)="selectThisExperiment(experiment);"></app-item-experiment> + </li> + </ul> + </div> + + <h2 class="mt-5 mb-2">2. Izaberite model</h2> + <app-model-load (selectedModelChangeEvent)="selectModel($event)" [forExperiment]="selectedExperiment"></app-model-load> + + <h2 class="my-5">3. Treniranje modela</h2> - <div class="d-flex flex-row justify-content-center align-items-center my-3"> - <button #trainButton class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj + <div class="d-flex flex-row justify-content-center align-items-center my-3"> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj model</button> + </div> + + <h2 class="mt-5">Rezultati treniranja</h2> + <div class="m-3" *ngIf="trainingResult"> + <h2 class="my-2">Rezultati treniranja:</h2> + <p> + {{trainingResult}} + </p> + </div> </div> - <h2 class="mt-5">Rezultati treniranja</h2> - <div class="m-3" *ngIf="trainingResult"> - <h2 class="my-2">Rezultati treniranja:</h2> - <app-metric-view *ngIf="trainingResult" [metrics]="trainingResult"></app-metric-view> - </div> - </div> - - <h2 class="mt-5">Rezultati treniranja</h2> - <div class="m-3" *ngIf="trainingResult"> - <h2 class="my-2">Rezultati treniranja:</h2> - <app-metric-view *ngIf="trainingResult" [metrics]="trainingResult"></app-metric-view> + <div class="col"></div> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.ts b/frontend/src/app/training/training.component.ts deleted file mode 100644 index a0bec8e2..00000000 --- a/frontend/src/app/training/training.component.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import Shared from '../Shared'; -import Experiment from '../_data/Experiment'; -import Model, { ProblemType } from '../_data/Model'; -import { MetricViewComponent } from '../_elements/metric-view/metric-view.component'; -import { ModelLoadComponent } from '../_elements/model-load/model-load.component'; -import { AuthService } from '../_services/auth.service'; -import { ExperimentsService } from '../_services/experiments.service'; -import { ModelsService } from '../_services/models.service'; -import { SignalRService } from '../_services/signal-r.service'; - -@Component({ - selector: 'app-training', - templateUrl: './training.component.html', - styleUrls: ['./training.component.css'] -}) -export class TrainingComponent implements OnInit { - - @ViewChild(ModelLoadComponent) modelLoadComponent?: ModelLoadComponent; - @ViewChild("trainButton") trainButtonRef!: ElementRef; - - @ViewChild(MetricViewComponent) metricViewComponent!: MetricViewComponent; - - myExperiments?: Experiment[]; - selectedExperiment?: Experiment; - selectedModel?: Model; - - trainingResult: any; - - history: any[] = []; - - term: string = ""; - - constructor(private modelsService: ModelsService, private route: ActivatedRoute, private experimentsService: ExperimentsService, private authService: AuthService, private signalRService: SignalRService) { - } - - ngOnInit(): void { - this.route.queryParams.subscribe(params => { - let experimentId = this.route.snapshot.paramMap.get("id"); - - this.fetchExperiments(experimentId); - - this.authService.loggedInEvent.subscribe(_ => { - this.fetchExperiments(experimentId); - - this.signalRService.startConnection(); - }); - - console.log(this.signalRService.hubConnection); - if (this.signalRService.hubConnection) { - this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { - console.log(this.selectedModel?._id, mId); - if (this.selectedModel?._id == mId) { - stat = stat.replace(/'/g, '"'); - this.trainingResult = JSON.parse(stat); - //console.log('JSON', this.trainingResult); - this.history.push(this.trainingResult); - this.metricViewComponent.update(this.history); - } - }); - } - }); - } - - fetchExperiments(andSelectWithId: string | null = '') { - this.experimentsService.getMyExperiments().subscribe((experiments) => { - this.myExperiments = experiments.reverse(); - - this.selectedExperiment = this.myExperiments.filter(x => x._id == andSelectWithId)[0]; - if (this.modelLoadComponent) - this.modelLoadComponent.newModel.type = this.selectedExperiment.type; - }); - } - - selectThisExperiment(experiment: Experiment) { - this.selectedExperiment = experiment; - if (this.modelLoadComponent) - this.modelLoadComponent.newModel.type = this.selectedExperiment.type; - } - - selectModel(model: Model) { - this.selectedModel = model; - } - - trainModel() { - this.trainingResult = undefined; - - if (this.selectedExperiment == undefined) { - Shared.openDialog("Greška", "Molimo Vas da izaberete eksperiment iz kolekcije."); - return; - } - if (this.selectedModel == undefined) { - Shared.openDialog("Greška", "Molimo Vas da izaberete model."); - return; - } - this.trainButtonRef.nativeElement.disabled = true; - this.modelsService.trainModel(this.selectedModel._id, this.selectedExperiment._id).subscribe((response: any) => { - //console.log('Train model complete!', response); - this.trainButtonRef.nativeElement.disabled = false; - Shared.openDialog("Obaveštenje", "Treniranje modela je počelo!"); - this.trainingResult = response; - }); - } -} |