aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/app')
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.css48
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.html30
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts396
-rw-r--r--frontend/src/app/_elements/column-table/column-table.component.html45
-rw-r--r--frontend/src/app/_elements/column-table/column-table.component.ts33
-rw-r--r--frontend/src/app/_elements/folder/folder.component.css27
-rw-r--r--frontend/src/app/_elements/folder/folder.component.html65
-rw-r--r--frontend/src/app/_elements/folder/folder.component.ts221
-rw-r--r--frontend/src/app/_elements/form-dataset/form-dataset.component.html12
-rw-r--r--frontend/src/app/_elements/form-dataset/form-dataset.component.ts6
-rw-r--r--frontend/src/app/_elements/form-model/form-model.component.html15
-rw-r--r--frontend/src/app/_elements/form-model/form-model.component.ts17
-rw-r--r--frontend/src/app/_elements/form-predictor/form-predictor.component.css24
-rw-r--r--frontend/src/app/_elements/form-predictor/form-predictor.component.html32
-rw-r--r--frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts25
-rw-r--r--frontend/src/app/_elements/form-predictor/form-predictor.component.ts42
-rw-r--r--frontend/src/app/_elements/graph/graph.component.css11
-rw-r--r--frontend/src/app/_elements/graph/graph.component.html12
-rw-r--r--frontend/src/app/_elements/graph/graph.component.ts5
-rw-r--r--frontend/src/app/_elements/metric-view/metric-view.component.html21
-rw-r--r--frontend/src/app/_elements/notifications/notifications.component.html2
-rw-r--r--frontend/src/app/_modals/share-dialog/share-dialog.component.css0
-rw-r--r--frontend/src/app/_modals/share-dialog/share-dialog.component.html14
-rw-r--r--frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts25
-rw-r--r--frontend/src/app/_modals/share-dialog/share-dialog.component.ts70
-rw-r--r--frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css21
-rw-r--r--frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html24
-rw-r--r--frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts25
-rw-r--r--frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts68
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.css30
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.html27
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.ts104
-rw-r--r--frontend/src/app/_pages/page-dataset/page-dataset.component.css0
-rw-r--r--frontend/src/app/_pages/page-dataset/page-dataset.component.html4
-rw-r--r--frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts25
-rw-r--r--frontend/src/app/_pages/page-dataset/page-dataset.component.ts45
-rw-r--r--frontend/src/app/_pages/page-model/page-model.component.css0
-rw-r--r--frontend/src/app/_pages/page-model/page-model.component.html6
-rw-r--r--frontend/src/app/_pages/page-model/page-model.component.spec.ts25
-rw-r--r--frontend/src/app/_pages/page-model/page-model.component.ts41
-rw-r--r--frontend/src/app/_services/datasets.service.ts37
-rw-r--r--frontend/src/app/_services/models.service.ts23
-rw-r--r--frontend/src/app/_services/predictors.service.ts4
-rw-r--r--frontend/src/app/app-routing.module.ts6
-rw-r--r--frontend/src/app/app.module.ts10
45 files changed, 1458 insertions, 265 deletions
diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css
index 2eea561e..60b341d0 100644
--- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css
+++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css
@@ -1,11 +1,47 @@
-canvas{
+.canvas-container {
+ height: 50%;
+ margin: 5px;
+}
- width:100% !important;
- height:90% !important;
+.bottom {
+ height: 30px;
+}
+
+canvas {
+ max-height: 100%;
border: 1px solid var(--ns-primary);
background-color: var(--ns-bg-dark-100);
border-radius: 5px;
- margin: 10px;
+ margin: 0;
font-size: 11 !important;
- }
- \ No newline at end of file
+ padding: 0;
+}
+
+.ns-col {
+ margin: 5px;
+}
+
+.hide {
+ display: none !important;
+ background-color: red;
+}
+
+.dl-button {
+ position: relative;
+ color: var(--offwhite);
+ border-radius: 4px;
+ border: 1px solid var(--ns-primary);
+ background-color: var(--ns-bg-dark-50);
+ margin: 5px;
+ padding: 10px;
+ cursor: pointer;
+ z-index: 1001;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+}
+
+.dl-button:hover {
+ background-color: var(--ns-primary);
+} \ No newline at end of file
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
index 1c711562..505dd50b 100644
--- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html
+++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html
@@ -1,4 +1,28 @@
-<div #wrapper class="position-relative" style="width:100%;height:95%;">
- <canvas id="myChart" #canvas>
- </canvas>
+<div #wrapper class="position-relative" style="width:100%;height:95%; border: 1px solid var(--ns-accent); background-color: var(--ns-bg-dark-100); border-radius: 15px;">
+ <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type !== ProblemType.Regression}" style="width: 100%; height: 90%;">
+ <div class="canvas-container">
+ <canvas #myChartmae>
+ </canvas>
+ </div>
+ <div class="canvas-container">
+ <canvas #myChartmse>
+ </canvas>
+ </div>
+ </div>
+ <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type === ProblemType.Regression}" style="width: 100%; height: 90%;">
+ <div class="canvas-container">
+ <canvas #myChartloss>
+ </canvas>
+ </div>
+ <div class="canvas-container">
+ <canvas #myChartacc>
+ </canvas>
+ </div>
+ </div>
+ <div class="bottom d-flex flex-row" style="justify-content: space-between; align-items: center;">
+ <h3 class="mt-5 mx-2"><span *ngIf="modelName.length > 0 && predictor">Model treniran za konfiguraciju: {{modelName}}</span><span *ngIf="modelName.length > 0 && !predictor">Model se trenira za konfiguraciju: {{modelName}}</span></h3>
+ <button *ngIf="predictor" class="mt-5 mx-2 btn-clear dl-button d-flex flex-row" (click)="downloadFile();" style="display: inline-block;" matTooltip="Preuzmi H5" matTooltipPosition="above">
+ <mat-icon>download</mat-icon> <div>H5</div>
+ </button>
+ </div>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts
index 7d21129c..b2eec377 100644
--- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts
+++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts
@@ -1,5 +1,10 @@
-import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
+import { Component, AfterViewInit, ElementRef, ViewChild, Input, ViewChildren, QueryList } from '@angular/core';
import { Chart } from 'chart.js';
+import FileSaver from 'file-saver';
+import Experiment from 'src/app/_data/Experiment';
+import Model, { ProblemType } from 'src/app/_data/Model';
+import Predictor from 'src/app/_data/Predictor';
+import { PredictorsService } from 'src/app/_services/predictors.service';
@Component({
selector: 'app-line-chart',
@@ -13,35 +18,53 @@ export class LineChartComponent implements AfterViewInit {
dataMAE: number[] = [];
dataMSE: number[] = [];
dataLOSS: number[] = [];
- dataValAcc:number[]=[];
- dataValMAE:number[]=[];
- dataValMSE:number[]=[];
- dataValLoss:number[]=[];
+ dataValAcc: number[] = [];
+ dataValMAE: number[] = [];
+ dataValMSE: number[] = [];
+ dataValLoss: number[] = [];
dataEpoch: number[] = [];
@ViewChild('wrapper')
wrapper!: ElementRef;
- @ViewChild('canvas')
- canvas!: ElementRef;
- constructor() {
-
+ @ViewChild('myChartacc') myChartacc!: ElementRef;
+ @ViewChild('myChartloss') myChartloss!: ElementRef;
+ @ViewChild('myChartmse') myChartmse!: ElementRef;
+ @ViewChild('myChartmae') myChartmae!: ElementRef;
+
+ @Input() experiment!: Experiment;
+ @Input() predictor?: Predictor;
+
+
+ downloadFile() {
+ if (!this.predictor) {
+ return;
+ }
+
+ const fileId = this.predictor.h5FileId;
+ if (fileId != undefined)
+ this.predictorsService.downloadH5(fileId).subscribe((response) => {
+ FileSaver.saveAs(response, fileId + '.h5');
+ });
+ }
+
+ constructor(private predictorsService: PredictorsService) {
+
}
width = 700;
height = 400;
-
- myChart!: Chart;
+ history: any[] = [];
+ myChartAcc!: Chart;
+ myChartMae!: Chart;
+ myChartMse!: Chart;
+ myChartLoss!: Chart;
+ ProblemType = ProblemType;
resize() {
- this.width = this.wrapper.nativeElement.offsetWidth;
+ //this.width = this.wrapper.nativeElement.offsetWidth;
this.height = this.wrapper.nativeElement.offsetHeight;
-
- if (this.canvas) {
- this.canvas.nativeElement.width = this.width;
- this.canvas.nativeElement.height = this.height;
- }
}
- update(myEpochs: number[], myAcc: number[], myLoss: number[], myMae: number[], myMse: number[], myValAcc:number[],myValLoss:number[],myValMae:number[],myValMse:number[]) {
-
+ update(myEpochs: number[], myAcc: number[], myLoss: number[], myMae: number[], myMse: number[], myValAcc: number[], myValLoss: number[], myValMae: number[], myValMse: number[]) {
+
this.dataEpoch.length = 0;
this.dataEpoch.push(...myEpochs);
@@ -69,14 +92,68 @@ export class LineChartComponent implements AfterViewInit {
this.dataValMSE.length = 0;
this.dataValMSE.push(...myValMse);
- this.myChart.update();
+ this.myChartAcc.update();
+ this.myChartLoss.update();
+ this.myChartMae.update();
+ this.myChartMse.update();
}
+ updateAll(history: any[], totalEpochs: number) {
+ const myAcc: number[] = [];
+ const myMae: number[] = [];
+ const myMse: number[] = [];
+ const myLoss: number[] = [];
+ const myValLoss: number[] = [];
+ const myValAcc: number[] = [];
+ const myValMAE: number[] = [];
+ const myValMSE: number[] = [];
+ const myEpochs: number[] = [];
+ this.history = history;
+ this.history.forEach((metrics, epoch) => {
+ if (totalEpochs > 100) {
+ let epochEstimate = epoch * Math.round(Math.sqrt(totalEpochs))
+ if (epochEstimate > totalEpochs)
+ epochEstimate = totalEpochs;
+ myEpochs.push(epochEstimate);
+ }
+ else
+ myEpochs.push(epoch + 1);
+ for (let key in metrics) {
+ let value = metrics[key];
+ if (key === 'accuracy') {
+ myAcc.push(parseFloat(value));
+ }
+ else if (key === 'loss') {
+ myLoss.push(parseFloat(value));
+ }
+ else if (key === 'mae') {
+ myMae.push(parseFloat(value));
+ }
+ else if (key === 'mse') {
+ myMse.push(parseFloat(value));
+ }
+ else if (key === 'val_acc') {
+ myValAcc.push(parseFloat(value));
+ }
+ else if (key === 'val_loss') {
+ myValLoss.push(parseFloat(value));
+ }
+ else if (key === 'val_mae') {
+ myValMAE.push(parseFloat(value));
+ }
+ else if (key === 'val_mse') {
+ myValMSE.push(parseFloat(value));
+ }
+ }
+ });
+
+ this.update(myEpochs, myAcc, myLoss, myMae, myMse, myValAcc, myValLoss, myValMAE, myValMSE);
+ }
ngAfterViewInit(): void {
-
+
window.addEventListener('resize', () => { this.resize() });
this.resize();
- this.myChart = new Chart("myChart",
+ this.myChartAcc = new Chart(this.myChartacc.nativeElement,
{
type: 'line',
data: {
@@ -86,58 +163,244 @@ export class LineChartComponent implements AfterViewInit {
label: 'Accuracy',
data: this.dataAcc,
borderWidth: 1,
-
+
},
{
label: 'Val_Accuracy',
data: this.dataValAcc,
borderWidth: 1
+ }
+ ]
+ },
+ options: {
+ //responsive: true,
+ //maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ labels: {
+ // This more specific font property overrides the global property
+ color: 'white',
+ font: {
+ size: 10
+ }
+ }
+ }
},
- {
- label: 'Loss',
- data: this.dataLOSS,
- borderWidth: 1
+ scales: {
+ x: {
+ ticks: {
+ color: 'white'
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Epoha',
+ color: "white"
+ }
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ color: 'white'
+
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Vrednost',
+ color: "white"
+ }
+ }
+
},
- {
- label: 'Val_Loss',
- data: this.dataValLoss,
- borderWidth: 1
+ animation: {
+ duration: 0
+ }
+
+ }
+ },
+
+ );
+ if (this.experiment.type == ProblemType.BinaryClassification || this.experiment.type == ProblemType.MultiClassification) { }
+ this.myChartLoss = new Chart(this.myChartloss.nativeElement,
+ {
+ type: 'line',
+ data: {
+ labels: this.dataEpoch,
+ datasets: [
+ {
+ label: 'Loss',
+ data: this.dataLOSS,
+ borderWidth: 1
+ },
+ {
+ label: 'Val_Loss',
+ data: this.dataValLoss,
+ borderWidth: 1
+ },
+ ]
+ },
+ options: {
+ //responsive: true,
+ //maintainAspectRatio: true,
+
+ plugins: {
+ legend: {
+ labels: {
+ // This more specific font property overrides the global property
+ color: 'white',
+ font: {
+ size: 10
+ }
+ }
+ }
},
- {
- label: 'MAE',
- data: this.dataMAE,
- borderWidth: 1
+ scales: {
+ x: {
+ ticks: {
+ color: 'white'
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Epoha',
+ color: "white"
+ }
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ color: 'white'
+
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Vrednost',
+ color: "white"
+ }
+ }
+
},
- {
- label: 'Val_MAE',
- data: this.dataValMAE,
- borderWidth: 1
+ animation: {
+ duration: 0
+ }
+
+ }
+ },
+
+ );
+ this.myChartMse = new Chart(this.myChartmse.nativeElement,
+ {
+ type: 'line',
+ data: {
+ labels: this.dataEpoch,
+ datasets: [
+ {
+ label: 'MSE',
+ data: this.dataMSE,
+ borderWidth: 1
+ },
+ {
+ label: 'Val_MSE',
+ data: this.dataValMSE,
+ borderWidth: 1
+ }
+ ]
+ },
+ options: {
+ //responsive: true,
+ //maintainAspectRatio: true,
+
+ plugins: {
+ legend: {
+ labels: {
+ // This more specific font property overrides the global property
+ color: 'white',
+ font: {
+ size: 10
+ }
+ }
+ }
},
- {
- label: 'MSE',
- data: this.dataMSE,
- borderWidth: 1
+ scales: {
+ x: {
+ ticks: {
+ color: 'white'
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Epoha',
+ color: "white"
+ }
+ },
+ y: {
+ beginAtZero: true,
+ ticks: {
+ color: 'white'
+
+ },
+ grid: {
+ color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Vrednost',
+ color: "white"
+ }
+ }
+
},
- {
- label: 'Val_MSE',
- data: this.dataValMSE,
- borderWidth: 1
+ animation: {
+ duration: 0
}
+
+ }
+ },
+
+ );
+ this.myChartMae = new Chart(this.myChartmae.nativeElement,
+ {
+ type: 'line',
+ data: {
+ labels: this.dataEpoch,
+ datasets: [
+ {
+ label: 'MAE',
+ data: this.dataMAE,
+ borderWidth: 1
+ },
+ {
+ label: 'Val_MAE',
+ data: this.dataValMAE,
+ borderWidth: 1
+ },
]
},
options: {
- responsive: true,
- maintainAspectRatio: true,
+ //responsive: true,
+ //maintainAspectRatio: true,
plugins: {
legend: {
- labels: {
- // This more specific font property overrides the global property
- color:'white',
- font: {
- size: 10
- }
+ labels: {
+ // This more specific font property overrides the global property
+ color: 'white',
+ font: {
+ size: 10
}
+ }
}
},
scales: {
@@ -148,15 +411,25 @@ export class LineChartComponent implements AfterViewInit {
grid: {
color: "rgba(0, 99, 171, 0.5)"
},
+ title: {
+ display: true,
+ text: 'Epoha',
+ color: "white"
+ }
},
y: {
beginAtZero: true,
ticks: {
color: 'white'
-
+
},
grid: {
color: "rgba(0, 99, 171, 0.5)"
+ },
+ title: {
+ display: true,
+ text: 'Vrednost',
+ color: "white"
}
}
@@ -166,8 +439,17 @@ export class LineChartComponent implements AfterViewInit {
}
}
- }
+ },
+
);
+
+ }
+
+ modelName: string = '';
+
+ setName(name: string) {
+ this.modelName = name;
+ this.predictor = undefined;
}
}
diff --git a/frontend/src/app/_elements/column-table/column-table.component.html b/frontend/src/app/_elements/column-table/column-table.component.html
index e6a781be..27e0f3bf 100644
--- a/frontend/src/app/_elements/column-table/column-table.component.html
+++ b/frontend/src/app/_elements/column-table/column-table.component.html
@@ -27,7 +27,9 @@
<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}}&nbsp;&nbsp;{{colInfo.columnName}}
+ <div class="text-overflow">
+ #{{i + 1}}&nbsp;&nbsp;{{colInfo.columnName}}
+ </div>
<mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox>
</div>
</th>
@@ -63,7 +65,9 @@
<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}}&nbsp;&nbsp;{{colInfo.columnName}}
+ <div class="text-overflow">
+ #{{i + 1}}&nbsp;&nbsp;{{colInfo.columnName}}
+ </div>
<mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox>
</div>
</th>
@@ -72,7 +76,7 @@
<tbody>
<tr *ngFor="let row of dataset.cMatrix; let i = index">
<th [ngClass]="{'header-disabled col-disabled' : !columnsChecked[i]}">
- <div class="text-left">
+ <div class="text-left text-overflow">
{{dataset.columnInfo[i].columnName}}
</div>
</th>
@@ -97,7 +101,9 @@
<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}}&nbsp;&nbsp;{{colInfo.columnName}}
+ <div class="text-overflow">
+ #{{i + 1}}&nbsp;&nbsp;{{colInfo.columnName}}
+ </div>
<mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox>
</div>
</th>
@@ -109,7 +115,7 @@
<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-select matNativeControl [(value)]="this.experiment.columnTypes[i]" [disabled]="!columnsChecked[i]" (selectionChange)="columnTypeChanged(colInfo.columnName, i);">
<mat-option [value]="ColumnType.categorical">Kategorijski</mat-option>
<mat-option [value]="ColumnType.numerical">Numerički</mat-option>
</mat-select>
@@ -141,8 +147,8 @@
</div>
</td>
</tr>
- <tr style="padding: 0">
- <th class="brighter cell-align long" (click)="openEncodingDialog()">
+ <tr style="padding: 0;" *ngIf="experiment.encodings!=undefined">
+ <th style="background-color: var(--ns-primary);" (click)="openEncodingDialog()">
<span class="verticalAlign">Enkodiranje</span>&nbsp;
<span class="material-icons-round verticalAlign rotate">settings</span>
</th>
@@ -168,11 +174,13 @@
<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>
+ <div class="cell-align">
+ <div [ngClass]="{'text-overflow' : experiment.nullValuesReplacers[i].option == NullValueOptions.Replace}">
+ {{nullValOption[i]}}
+ </div>
+ <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>
@@ -180,12 +188,12 @@
</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="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button>
+ <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button>
+ <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button>
+ <button *ngIf="experiment.columnTypes[i] == ColumnType.numerical" 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 *ngIf="experiment.columnTypes[i] == ColumnType.categorical" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button>
<button mat-menu-item [matMenuTriggerFor]="replaceWith">Unesi vrednost...</button>
</mat-menu>
@@ -195,7 +203,8 @@
</mat-menu>
<mat-menu #replaceWith="matMenu">
- <input type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue>
+ <input *ngIf="experiment.columnTypes[i] == ColumnType.categorical" type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value] #enterAValue>
+ <input *ngIf="experiment.columnTypes[i] == ColumnType.numerical" type="number" 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>
diff --git a/frontend/src/app/_elements/column-table/column-table.component.ts b/frontend/src/app/_elements/column-table/column-table.component.ts
index 4b10401d..96818976 100644
--- a/frontend/src/app/_elements/column-table/column-table.component.ts
+++ b/frontend/src/app/_elements/column-table/column-table.component.ts
@@ -7,7 +7,7 @@ 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 { NullValReplacer, 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';
@@ -15,6 +15,7 @@ import Shared from 'src/app/Shared';
import { PieChartComponent } from '../_charts/pie-chart/pie-chart.component';
import { BoxPlotComponent } from '../_charts/box-plot/box-plot.component';
import { ActivatedRoute } from '@angular/router';
+import { UpdateExperimentDialogComponent } from 'src/app/_modals/update-experiment-dialog/update-experiment-dialog.component';
@Component({
selector: 'app-column-table',
@@ -87,6 +88,7 @@ export class ColumnTableComponent implements AfterViewInit {
if (this.route.snapshot.paramMap.get("id") == null && this.route.snapshot.paramMap.get("predictorId") == null) {
this.dataset = dataset;
this.setColumnTypeInitial();
+ this.resetColumnEncodings(Encoding.Label);
this.columnsChecked = [];
this.dataset.columnInfo.forEach(column => {
@@ -95,7 +97,6 @@ export class ColumnTableComponent implements AfterViewInit {
this.resetInputColumns();
this.resetOutputColumn();
- this.resetColumnEncodings(Encoding.Label);
this.setDeleteRowsForMissingValTreatment();
this.nullValOption = [];
@@ -180,8 +181,13 @@ export class ColumnTableComponent implements AfterViewInit {
this.columnTableChanged.emit();
}
- columnTypeChanged(columnName: string) {
- if (this.experiment.outputColumn == columnName)
+ columnTypeChanged(columnName: string, colIndex: number) {
+
+ this.experiment.nullValuesReplacers[colIndex].option = NullValueOptions.DeleteRows;
+ this.experiment.nullValuesReplacers[colIndex].value = "";
+ this.nullValOption[colIndex] = "Obriši redove (" + this.dataset?.columnInfo[colIndex].numNulls + ")";
+
+ if (this.experiment.outputColumn == columnName)
this.changeProblemType();
else
this.columnTableChangeDetected();
@@ -252,9 +258,11 @@ export class ColumnTableComponent implements AfterViewInit {
resetColumnEncodings(encodingType: Encoding) {
if (this.experiment != undefined && this.dataset != undefined) {
this.experiment.encodings = [];
+ console.log("prvi: RESET COLUMN ENC, DUZINA ENCODINGS NIZA:", this.experiment.encodings.length);
for (let i = 0; i < this.dataset.columnInfo.length; i++) {
this.experiment.encodings.push(new ColumnEncoding(this.dataset?.columnInfo[i].columnName, encodingType));
}
+ console.log("drugi: RESET COLUMN ENC, DUZINA ENCODINGS NIZA:", this.experiment.encodings.length);
this.columnTableChangeDetected();
}
}
@@ -360,16 +368,27 @@ export class ColumnTableComponent implements AfterViewInit {
Object.assign(this.experiment, experiment);
this.experiment._columnsSelected = true;
this.experimentChanged.emit();
+ this.okPressed.emit();
}
});
}
openUpdateExperimentDialog() {
- this.experimentService.updateExperiment(this.experiment).subscribe((response) => {
- Object.assign(this.experiment, response);
+ const dialogRef = this.dialog.open(UpdateExperimentDialogComponent, {
+ width: '350px',
+ data: { experiment: this.experiment }
+ });
+ dialogRef.afterClosed().subscribe(experiment => {
+ if (experiment == undefined)
+ return;
+ if (this.experiment._id != experiment._id)
+ Shared.openDialog("Novi eksperiment", "Uspešno ste sačuvali novi eksperiment. Nastavite rad na njemu.");
+ else
+ Shared.openDialog("Izmena eksperimenta", "Uspešno ste izmenili podatke o eksperimentu.");
+ Object.assign(this.experiment, experiment);
this.experiment._columnsSelected = true;
this.experimentChanged.emit();
- Shared.openDialog("Izmena eksperimenta", "Uspešno ste izmenili podatke o eksperimentu.");
+ this.okPressed.emit();
});
}
diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css
index 682fc645..b2363816 100644
--- a/frontend/src/app/_elements/folder/folder.component.css
+++ b/frontend/src/app/_elements/folder/folder.component.css
@@ -1,6 +1,5 @@
#folder {
width: 100%;
-
}
#tabs {
@@ -184,11 +183,18 @@
}
.file-bottom-buttons {
- position: absolute;
- bottom: 5px;
- right: 4%;
+ position: relative;
+ display: flex;
+ flex-direction: row-reverse;
+ height: 0;
+}
+
+.file-bottom-buttons-helper {
+ position: relative;
display: flex;
flex-direction: row-reverse;
+ width: fit-content;
+ height: 45px;
}
.file-button {
@@ -196,10 +202,11 @@
color: var(--offwhite);
border-radius: 4px;
border: 1px solid var(--ns-primary);
+ background-color: var(--ns-bg-dark-50);
margin: 5px;
padding: 5px;
cursor: pointer;
- z-index: 1001;
+ z-index: 801;
display: flex;
flex-direction: row;
align-items: center;
@@ -221,4 +228,14 @@
.highlight-exp {
/*font-size: 16px;*/
font-weight: 700;
+}
+
+.text-overflow {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.selected-file-tab {
+ max-width: 230px !important;
} \ No newline at end of file
diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html
index a557edde..e77e05a3 100644
--- a/frontend/src/app/_elements/folder/folder.component.html
+++ b/frontend/src/app/_elements/folder/folder.component.html
@@ -13,8 +13,8 @@
<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 class="folder-tab p-1 rounded-top selected-file-tab" *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 text-overflow" (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">
@@ -58,19 +58,13 @@
<!--{{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 && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)">
- <mat-icon matTooltip="Obriši" matTooltipPosition="right">delete</mat-icon>
- </button>
- <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(this.selectedFile,$event)" style="display: inline-block;">
- <mat-icon matTooltip="Preuzmi" matTooltipPosition="before">download</mat-icon>
- </button>
- <!-- <button class="btn-clear file-button">
- <mat-icon>zoom_out_map</mat-icon>
- </button> -->
- </div>
- <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)" (editEvent)="onFileChange()"></app-form-model>
- <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset>
+ <!-- New File -->
+ <app-form-model #newModel [ngClass]="{'form-hidden': type != FolderType.Model || !newFileSelected}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)"></app-form-model>
+ <app-form-dataset #newDataset [ngClass]="{'form-hidden': type != FolderType.Dataset || !newFileSelected}" [forExperiment]="forExperiment"></app-form-dataset>
+ <!-- Selected File -->
+ <app-form-model #selectedModel [ngClass]="{'form-hidden': type != FolderType.Model || newFileSelected}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)"
+ (editEvent)="onFileChange()"></app-form-model>
+ <app-form-dataset #selectedDataset [ngClass]="{'form-hidden': type != FolderType.Dataset || newFileSelected}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset>
</div>
<div [ngClass]="{'form-hidden' : !listView}" class="list-view">
<div *ngFor="let file of filteredFiles; let i = index">
@@ -82,16 +76,19 @@
{{file.lastUpdated | date}}
</div>
<div class="mx-2 hover-show" *ngIf="selectedTab !== TabType.PublicDatasets && selectedTab !== TabType.PublicModels">
- <button *ngIf="selectedTab==TabType.MyDatasets" class="btn-clear file-button" (click)="downloadFile(file,$event)" style="display: inline-block;">
- <mat-icon matTooltip="Preuzmi" matTooltipPosition="before">download</mat-icon>
+ <button *ngIf="selectedTab==TabType.MyDatasets || selectedTab==TabType.MyModels" class="btn-clear file-button" (click)="shareFile(file,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above">
+ <mat-icon>share</mat-icon>
+ </button>
+ <button *ngIf="selectedTab==TabType.MyDatasets" class="btn-clear file-button" (click)="downloadFile(file,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above">
+ <mat-icon>download</mat-icon>
</button>
- <button class="btn-clear file-button" (click)="deleteFile(file, $event)" style="display: inline-block;">
- <mat-icon matTooltip="Obriši" matTooltipPosition="right">delete</mat-icon>
+ <button class="btn-clear file-button" (click)="deleteFile(file, $event)" style="display: inline-block;" matTooltip="Obriši" matTooltipPosition="above">
+ <mat-icon>delete</mat-icon>
</button>
</div>
- <div class="mx-2 hover-show" *ngIf="selectedTab == TabType.PublicDatasets || selectedTab == TabType.PublicModels">
+ <div class="mx-2 hover-show" *ngIf="selectedTab == TabType.PublicDatasets || selectedTab == TabType.PublicModels" matTooltip="Uvezi" matTooltipPosition="right">
<button class="btn-clear file-button" (click)="addFile(file, $event)">
- <mat-icon matTooltip="Uvezi" matTooltipPosition="right">note_add</mat-icon>
+ <mat-icon>note_add</mat-icon>
</button>
</div>
</div>
@@ -105,7 +102,7 @@
</div>
</div>
<div class="mx-2 hover-hide">
- {{predictor.lastUpdated | date}}
+ {{predictor.dateCreated| date}}
</div>
<div class="mx-2 hover-show">
<button class="btn-clear file-button" (click)="deleteFile(predictor, $event, true)">
@@ -116,7 +113,7 @@
</div>
</div>
- <div class="list-add" [ngSwitch]="type" *ngIf="privacy != Privacy.Public" >
+ <div class="list-add" [ngSwitch]="type" *ngIf="privacy != Privacy.Public">
<!-- {{privacy == Privacy.Public ? 'javni ' : ' '}} -->
<button mat-raised-button *ngSwitchCase="FolderType.Dataset" (click)="selectNewFile()">Dodaj izvor podataka</button>
<button mat-raised-button *ngSwitchCase="FolderType.Model" (click)="selectNewFile()">Dodaj konfiguraciju neuronske mreže</button>
@@ -125,8 +122,26 @@
</div>
</div>
</div>
+ <div [ngSwitch]="newFileSelected" *ngIf="!listView">
+ <div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile">
+ <div class="file-bottom-buttons-helper">
+ <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(selectedFile, $event)" matTooltip="Obriši" matTooltipPosition="above">
+ <mat-icon>delete</mat-icon>
+ </button>
+ <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above">
+ <mat-icon>download</mat-icon>
+ </button>
+ <button *ngIf="this.selectedFile && selectedTab == TabType.File" class="btn-clear file-button" (click)="shareFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above">
+ <mat-icon>share</mat-icon>
+ </button>
+ </div>
+ <!-- <button class="btn-clear file-button">
+ <mat-icon>zoom_out_map</mat-icon>
+ </button> -->
+ </div>
+ </div>
<div id="footer" [ngSwitch]="newFileSelected" *ngIf="!listView">
- <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="true">
+ <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" [disabled]="saveDisabled" *ngSwitchCase="true">
<div class="f-row">
<div>Sačuvaj</div>
<div class="pt-1">
@@ -160,4 +175,6 @@
</button>
</ng-container>
</div>
+
+
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts
index c9252caa..9dfabdb3 100644
--- a/frontend/src/app/_elements/folder/folder.component.ts
+++ b/frontend/src/app/_elements/folder/folder.component.ts
@@ -15,6 +15,8 @@ import { ActivatedRoute, Router } from '@angular/router';
import Predictor from 'src/app/_data/Predictor';
import FileSaver from 'file-saver';
import isEqual from 'lodash.isequal';
+import { ShareDialogComponent } from 'src/app/_modals/share-dialog/share-dialog.component';
+import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'app-folder',
@@ -23,8 +25,10 @@ import isEqual from 'lodash.isequal';
})
export class FolderComponent implements AfterViewInit {
- @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent;
- @ViewChild(FormModelComponent) formModel!: FormModelComponent;
+ @ViewChild('selectedDataset') formDataset!: FormDatasetComponent;
+ @ViewChild('selectedModel') formModel!: FormModelComponent;
+ @ViewChild('newDataset') formNewDataset!: FormDatasetComponent;
+ @ViewChild('newModel') formNewModel!: FormModelComponent;
@Input() folderName: string = 'Moji podaci';
@Input() files!: FolderFile[]
@@ -40,8 +44,7 @@ export class FolderComponent implements AfterViewInit {
selectedFileIndex: number = -1;
selectedFile?: FolderFile;
hoveringOverFileIndex: number = -1;
-
- fileToDisplay?: FolderFile;
+ saveDisabled: boolean = false;
@Output() selectedFileChanged: EventEmitter<FolderFile> = new EventEmitter();
@Output() fileFromRoute: EventEmitter<FolderFile> = new EventEmitter();
@@ -49,7 +52,7 @@ export class FolderComponent implements AfterViewInit {
searchTerm: string = '';
- constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute) {
+ constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute, public dialog: MatDialog) {
this.tabsToShow.forEach(tab => this.folders[tab] = []);
}
@@ -60,7 +63,10 @@ export class FolderComponent implements AfterViewInit {
this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => {
if (this.type == FolderType.Dataset) {
this.refreshFiles(dId);
+ this.okPressed.emit();
}
+ this.saveDisabled = false;
+ console.log("Notify dataset ", this.saveDisabled);
});
} else {
console.warn("Dataset-Load: No connection!");
@@ -68,59 +74,82 @@ export class FolderComponent implements AfterViewInit {
}
displayFile() {
- if (this.type == FolderType.Dataset) {
- this.formDataset.dataset = <Dataset>this.fileToDisplay;
- this.formDataset.existingFlag = false;
- }
- 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];
+ if (this.newFileSelected) {
+ if (this.type == FolderType.Dataset) {
+ this.formNewDataset.dataset = <Dataset>this.newFile;
+ this.formNewDataset.existingFlag = false;
+ }
+ else if (this.type == FolderType.Model)
+ this.formNewModel.newModel = <Model>this.newFile;
} else {
- if (this.newFileSelected) {
- this.fileToDisplay = this.newFile;
- } else {
- this.fileToDisplay = this.files[this.selectedFileIndex];
+ if (this.type == FolderType.Dataset) {
+ this.formDataset.dataset = <Dataset>this.selectedFile;
+ this.formDataset.existingFlag = false;
}
+ else if (this.type == FolderType.Model)
+ this.formModel.newModel = <Model>this.selectedFile;
}
- this.displayFile();*/
}
selectNewFile() {
if (!this.newFile) {
this.createNewFile();
}
- this.fileToDisplay = this.newFile;
this.newFileSelected = true;
this.listView = false;
this.displayFile();
+
+ this.selectedTab = TabType.NewFile;
+
if (this.type == FolderType.Dataset) {
- this.formDataset.clear();
+ this.formNewDataset.clear();
}
}
selectFile(file?: FolderFile) {
+ if (this.privacy == Privacy.Public) {
+ if (file) {
+ if (this.type == FolderType.Dataset) {
+ this.router.navigate(['dataset', file?._id]);
+ } else if (this.type == FolderType.Model) {
+ this.router.navigate(['model', file?._id]);
+ }
+ }
+
+ }
+
+
this.formDataset.resetPagging();
this.selectedFile = file;
- Object.assign(this.lastFileData, this.selectedFile);
- this.fileToDisplay = file;
+ this.updateLastFileData(file);
if (this.type == FolderType.Experiment && file) {
this.router.navigate(['/experiment/' + file._id]);
}
this.newFileSelected = false;
this.listView = false;
this.selectedFileChanged.emit(this.selectedFile);
- this.selectTab(TabType.File);
this.displayFile();
+ this.selectedTab = TabType.File;
+
if (this.type == FolderType.Dataset)
this.formDataset.loadExisting();
}
+ updateLastFileData(file: FolderFile | undefined) {
+ if (!file) return;
+
+ Object.assign(this.lastFileData, file);
+ if (this.type == FolderType.Model) {
+ const lastModel = (<Model>this.lastFileData)
+ lastModel.layers = [];
+ (<Model>file).layers.forEach(layer => {
+ const clone = Object.assign({}, layer);
+ lastModel.layers.push(clone);
+ })
+ }
+ }
+
goToExperimentPageWithPredictor(file: FolderFile, predictor: Predictor) {
this.router.navigate(['/experiment/p/' + predictor._id]);
}
@@ -128,6 +157,9 @@ export class FolderComponent implements AfterViewInit {
createNewFile() {
if (this.type == FolderType.Dataset) {
this.newFile = new Dataset();
+ this.formNewDataset.files = [];
+ this.formNewDataset.firstInput = false;
+ this.formNewDataset.filename = "";
} else if (this.type == FolderType.Model) {
this.newFile = new Model();
}
@@ -137,6 +169,22 @@ export class FolderComponent implements AfterViewInit {
this.okPressed.emit();
}
+ fileToShare: FolderFile | undefined = undefined;
+
+ shareFile(file: FolderFile, event: Event) {
+ event.stopPropagation();
+
+ this.fileToShare = file;
+
+ const dialogRef = this.dialog.open(ShareDialogComponent, {
+ width: '550px',
+ data: { file: this.fileToShare, fileType: this.type }
+ });
+ dialogRef.afterClosed().subscribe(experiment => {
+ this.refreshFiles();
+ });
+ }
+
_initialized: boolean = false;
refreshFiles(selectedDatasetId: string | null = null, selectedModelId: string | null = null) {
@@ -172,7 +220,9 @@ export class FolderComponent implements AfterViewInit {
if (!this._initialized) {
this.files = this.folders[this.startingTab];
this.filteredFiles = [];
- this.selectTab(this.startingTab);
+ setTimeout(() => {
+ this.selectTab(this.startingTab);
+ });
this._initialized = true;
}
}
@@ -229,35 +279,47 @@ export class FolderComponent implements AfterViewInit {
})
/* ------------------------------------------------ */
this.searchTermsChanged();
+ if (this.selectedTab == TabType.MyExperiments)
+ this.selectTab(TabType.MyExperiments);
})
});
});
}
saveNewFile() {
+ console.log("USAO U saveDisabled: ", this.saveDisabled);
+ if (this.saveDisabled) {
+ console.log("USAO U IF");
+ return;
+ }
+ this.saveDisabled = true;
this.loadingAction = true;
switch (this.type) {
case FolderType.Dataset:
- this.formDataset!.uploadDataset((dataset: Dataset) => {
+ this.formNewDataset!.uploadDataset((dataset: Dataset) => {
this.newFile = undefined;
this.loadingAction = false;
- this.okPressed.emit();
+ //this.okPressed.emit();
//Shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se obradi.");
this.refreshFiles();
+ this.createNewFile();
},
() => {
Shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset.");
+ this.saveDisabled = false;
});
break;
case FolderType.Model:
- this.formModel.newModel.type = this.formModel.forProblemType;
- this.modelsService.addModel(this.formModel.newModel).subscribe(model => {
+ this.formNewModel.newModel.type = this.formModel.forProblemType;
+ this.modelsService.addModel(this.formNewModel.newModel).subscribe(model => {
this.newFile = undefined;
this.loadingAction = false;
//Shared.openDialog("Obaveštenje", "Uspešno ste dodali novu konfiguraciju neuronske mreže u kolekciju.");
this.refreshFiles(null, model._id); // todo select model
+ this.createNewFile();
}, (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.");
+ this.saveDisabled = false;
});
break;
}
@@ -300,10 +362,29 @@ export class FolderComponent implements AfterViewInit {
onFileChange() {
setTimeout(() => {
- this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || isEqual(this.selectedFile, this.lastFileData));
+ this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || this.checkFileDataEqualToLastFileData());
});
}
+ checkFileDataEqualToLastFileData() {
+ if (this.type == FolderType.Model) {
+ let layersEqual = true;
+ const oldModel = (<Model>this.lastFileData);
+ const selectedModel = (<Model>this.selectedFile)
+ const oldLayers = oldModel.layers;
+ oldModel.layers = selectedModel.layers;
+ const objEqual = isEqual(this.selectedFile, oldModel);
+ oldLayers.forEach((layer, index) => {
+ if (!isEqual(layer, selectedModel.layers[index])) {
+ layersEqual = false;
+ }
+ });
+ return objEqual && layersEqual;
+ } else {
+ return isEqual(this.selectedFile, this.lastFileData);
+ }
+ }
+
updateFile() {
const file = this.selectedFile;
this.loadingAction = true;
@@ -324,7 +405,7 @@ export class FolderComponent implements AfterViewInit {
fileUpdatedSuccess() {
this.loadingAction = false;
this.selectedFileHasChanges = false;
- Object.assign(this.lastFileData, this.selectedFile);
+ this.updateLastFileData(this.selectedFile);
this.refreshFiles();
this.selectedFileChanged.emit(this.selectedFile);
}
@@ -336,22 +417,47 @@ export class FolderComponent implements AfterViewInit {
case FolderType.Dataset:
const dataset = <Dataset>file;
Shared.openYesNoDialog("Obriši izvor podataka", "Eksperimenti i trenirani modeli nad ovim izvorom podataka će takođe biti obrisani, da li ste sigurni da želite da obrišete izvor: " + dataset.name + "?", () => {
- this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
- this.files.splice(this.files.indexOf(file), 1);
+ if (this.selectedTab == TabType.MyDatasets) {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ }
this.loadingAction = true;
this.datasetsService.deleteDataset(dataset).subscribe((response) => {
this.loadingAction = false;
+ if (this.selectedTab == TabType.File) {
+ this.refreshDatasets(null);
+ this.selectedFile = undefined!;
+ setTimeout(() => {
+ this.selectTab(TabType.MyDatasets);
+ });
+ }
+ if (this.archive) {
+ this.refreshExperiments();
+ }
+
});
})
break;
case FolderType.Model:
const model = <Model>file;
Shared.openYesNoDialog("Obriši konfiguraciju neuronske mreže", "Trenirani modeli za ovu konfiguraciju će takođe biti obrisani, da li ste sigurni da želite da obrišete konfiguraciju: " + model.name + "?", () => {
- this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
- this.files.splice(this.files.indexOf(file), 1);
+ if (this.selectedTab == TabType.MyModels) {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ }
this.loadingAction = true;
this.modelsService.deleteModel(<Model>file).subscribe((response) => {
this.loadingAction = false;
+ if (this.selectedTab == TabType.File) {
+ this.refreshModels(null);
+ this.selectedFile = undefined!;
+ setTimeout(() => {
+ this.selectTab(TabType.MyModels);
+ });
+ }
+ if (this.archive) {
+ this.refreshExperiments();
+ }
});
})
@@ -370,11 +476,20 @@ export class FolderComponent implements AfterViewInit {
} else {
const experiment = <Experiment>file;
Shared.openYesNoDialog("Obriši eksperiment", "Trenirani modeli za ovaj eksperiment će takođe biti obrisani, da li ste sigurni da želite da obrišete eksperiment: " + experiment.name + "?", () => {
- this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
- this.files.splice(this.files.indexOf(file), 1);
+ if (this.selectedTab == TabType.MyExperiments) {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ }
this.loadingAction = true;
this.experimentsService.deleteExperiment(experiment).subscribe((response) => {
this.loadingAction = false;
+ if (this.selectedTab == TabType.File) {
+ this.refreshExperiments();
+ this.selectedFile = undefined!;
+ setTimeout(() => {
+ this.selectTab(TabType.MyExperiments);
+ });
+ }
});
});
}
@@ -461,21 +576,19 @@ export class FolderComponent implements AfterViewInit {
hoverTab: TabType = TabType.None;
selectTab(tab: TabType) {
- setTimeout(() => {
- if (tab == TabType.NewFile) {
- this.selectNewFile();
- this.selectedFile=undefined!;
- }
-
- 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.NewFile) {
+ this.selectNewFile();
+ } /*else if (tab == TabType.File) {
+ this.selectFile(this.selectedFile);
+ }*/
+ 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();
- });
+ if (tab !== TabType.File && tab !== TabType.NewFile)
+ this.searchTermsChanged();
}
getListView(tab: TabType) {
diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html
index 6194a1a8..b518f15c 100644
--- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html
+++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html
@@ -2,7 +2,11 @@
<div class="topBar">
<div class="kolona mb-3">
<div class="fileButton">
- <button type="button" mat-raised-button (click)="fileInput.click()"><span *ngIf="!firstInput">Dodaj izvor podataka</span><span *ngIf="firstInput">{{filename}}</span></button>
+ <button type="button" mat-raised-button (click)="fileInput.click()" [disabled]="dataset._id != '' || disableAll">
+ <span *ngIf="!firstInput && dataset._id == '' ">Dodaj izvor podataka</span>
+ <span *ngIf="firstInput && dataset._id == '' ">{{filename}}</span>
+ <span *ngIf="dataset._id != '' ">Fajl je odabran</span>
+ </button>
</div>
</div>
@@ -10,7 +14,7 @@
<div role="group">
<mat-form-field class="example-full-width" appearance="fill">
<mat-label>Naziv</mat-label>
- <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()">
+ <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()" [readonly]="disableAll">
<mat-error *ngIf="nameFormControl.hasError('required')">
@@ -22,7 +26,7 @@
<div class="kolona">
<mat-form-field appearance="fill">
<mat-label>Delimiter</mat-label>
- <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value=",">
+ <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value="," [disabled]="disableAll">
<mat-option *ngFor="let option of delimiterOptions" [value]="option">
{{ option }}
</mat-option>
@@ -38,7 +42,7 @@
<i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i>
- <input class="file" id="file-upload" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv">
+ <input class="file" id="file-upload" [disabled]="dataset._id != '' || disableAll" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv">
<div class="datatable">
<div [ngClass]="{'hidden': (!existingFlag)}" class="text-center">
diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
index 1a29f74e..3eb6fe39 100644
--- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
+++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
@@ -13,6 +13,7 @@ import { FormControl, Validators } from '@angular/forms';
styleUrls: ['./form-dataset.component.css']
})
export class FormDatasetComponent {
+ @Input() disableAll: boolean = false;
@ViewChild(DatatableComponent) datatable!: DatatableComponent;
@@ -89,7 +90,7 @@ export class FormDatasetComponent {
update() {
this.firstInput = true;
- if (this.files.length < 1){
+ if (this.files.length < 1) {
this.loadExisting();
return;
}
@@ -155,10 +156,11 @@ export class FormDatasetComponent {
uploadDataset(onSuccess: Function = (dataset: Dataset) => { }, onError: Function = () => { }) {
if (this.files[0] == undefined) {
shared.openDialog("Greška", "Niste izabrali fajl za učitavanje.");
+ onError();
return;
}
- return this.modelsService.uploadData(this.files[0]).subscribe((file) => {
+ return this.datasetsService.uploadData(this.files[0]).subscribe((file) => {
this.dataset._id = "";
this.dataset.fileId = file._id;
this.dataset.uploaderId = shared.userId;
diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html
index b986e154..1f0208fa 100644
--- a/frontend/src/app/_elements/form-model/form-model.component.html
+++ b/frontend/src/app/_elements/form-model/form-model.component.html
@@ -1,4 +1,4 @@
-<div *ngIf="newModel">
+<div *ngIf="newModel!=undefined">
<div id="container">
<div class="ns-row">
@@ -90,8 +90,8 @@
<!-- GRAF -->
<div class="m-2">
- <div class="row">
- <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px; width: fit-content;">
+ <div class="ns-row">
+ <div class="ns-col rounded" style="border:1px solid var(--ns-primary);margin-top: 10px; min-width: fit-content; max-width: 30%;">
<!-- <label>Veličina skupa za validaciju: </label><b>{{validationSize}} %</b>
<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 p-0">Trening ////slider////// Test</div>
@@ -120,9 +120,10 @@
</div>
</div>
- <div class="col-sm-9">
+ <div class="ns-col">
<!-- {{forExperiment._columnsSelected}} -->
- <app-graph [model]="newModel" [inputColumns]="getInputColumns()"></app-graph>
+
+ <app-graph [model]="newModel" [inputColumns]="getInputColumns()" [outputColumn]="(this.forExperiment!=undefined)?this.forExperiment.outputColumn:''"></app-graph>
</div>
</div>
</div>
@@ -189,7 +190,7 @@
<!-- LAYERI -->
- <div id="layers">
+ <div id="layers" style="margin-top: 10px;">
<div class="layer" *ngFor="let item of newModel.layers; let i=index">
@@ -229,7 +230,7 @@
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Stopa regularizacije</mat-label>
- <mat-select [(ngModel)]="newModel.layers[i].regularisationRate">
+ <mat-select [(ngModel)]="newModel.layers[i].regularisationRate" (selectionChange)="editEvent.emit();">
<mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option">
{{ optionName }}
</mat-option>
diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts
index 7c84d2ba..7831f573 100644
--- a/frontend/src/app/_elements/form-model/form-model.component.ts
+++ b/frontend/src/app/_elements/form-model/form-model.component.ts
@@ -12,6 +12,8 @@ import { MatSliderChange } from '@angular/material/slider';
styleUrls: ['./form-model.component.css']
})
export class FormModelComponent implements AfterViewInit {
+ @Input() disableAll: boolean = false;
+
@ViewChild(GraphComponent) graph!: GraphComponent;
@Input() forExperiment!: Experiment;
@Output() selectedModelChangeEvent = new EventEmitter<Model>();
@@ -25,11 +27,7 @@ export class FormModelComponent implements AfterViewInit {
@Output() editEvent = new EventEmitter();
ngAfterViewInit(): void {
- this.lossFunction = this.lossFunctions[this.forProblemType][0];
- this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0];
-
- this.newModel.lossFunction = this.lossFunction;
- this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction;
+
}
selectFormControl = new FormControl('', Validators.required);
@@ -80,10 +78,17 @@ export class FormModelComponent implements AfterViewInit {
loadModel(model: Model) {
this.newModel = model;
this.forProblemType = model.type;
+ this.lossFunction = this.lossFunctions[this.forProblemType][0];
+ this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0];
+
+ this.newModel.lossFunction = this.lossFunction;
+ this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction;
+ this.updateGraph();
}
updateGraph() {
- this.graph.update();
+ if(this.newModel)
+ this.graph.update();
}
removeLayer() {
diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.css b/frontend/src/app/_elements/form-predictor/form-predictor.component.css
new file mode 100644
index 00000000..569a9887
--- /dev/null
+++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.css
@@ -0,0 +1,24 @@
+th{
+ margin-right: 5px;
+}
+.container{
+ background-color: var(--ns-bg-dark-50);
+ border-radius: 10px;
+ border: 1px solid var(--ns-primary);
+ width: 100%;
+ height: 100%;}
+#input{
+ color: whitesmoke;
+ background-color: var(--ns-bg-dark-100);
+ margin-top: 10px;
+}
+h3{
+ color: whitesmoke;
+}
+#output{
+ background-color: var(--ns-bg-dark-100);
+ color:whitesmoke;
+}
+#output b{
+ height: 50px;
+}
diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.html b/frontend/src/app/_elements/form-predictor/form-predictor.component.html
new file mode 100644
index 00000000..3ebb666c
--- /dev/null
+++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.html
@@ -0,0 +1,32 @@
+<div class="container" id="predictWithInput" *ngIf="predictor">
+ <h2 style="margin-top:20px ;color:whitesmoke;">
+ Unesi vrednosti za predikciju
+ </h2>
+ <div class="row" id="input">
+ <div class="col">
+ <div class="row" style="margin-left:5px; font-weight:bold; color:whitesmoke">
+
+ </div>
+ <div class="row" *ngFor="let i of ['a','b','c','d','e','f']">
+ <div class="col">
+ <!--
+ <mat-form-field>
+ <mat-label>{{i}}</mat-label>
+ <input matInput placeholder="Vrednost">
+ </mat-form-field>-->
+ </div>
+ </div>
+
+ </div>
+
+
+ </div>
+
+ <div class="row justify-content-between">
+ <button mat-raised-button style="max-width:30%;margin: 20px;">Predvidi rezultat</button>
+ </div>
+ <div class="row" id="output">
+ <b>Izlazna vrednost za kolonu "output":"input"</b>
+ </div>
+
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts b/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts
new file mode 100644
index 00000000..f873e3af
--- /dev/null
+++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { FormPredictorComponent } from './form-predictor.component';
+
+describe('FormPredictorComponent', () => {
+ let component: FormPredictorComponent;
+ let fixture: ComponentFixture<FormPredictorComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ FormPredictorComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(FormPredictorComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_elements/form-predictor/form-predictor.component.ts b/frontend/src/app/_elements/form-predictor/form-predictor.component.ts
new file mode 100644
index 00000000..fbdce569
--- /dev/null
+++ b/frontend/src/app/_elements/form-predictor/form-predictor.component.ts
@@ -0,0 +1,42 @@
+import { Component, OnInit ,Input,Output, ViewChild} from '@angular/core';
+import Predictor from 'src/app/_data/Predictor';
+import {PredictorsService } from 'src/app/_services/predictors.service';
+import { ActivatedRoute } from '@angular/router';
+@Component({
+ selector: 'app-form-predictor',
+ templateUrl: './form-predictor.component.html',
+ styleUrls: ['./form-predictor.component.css']
+})
+export class FormPredictorComponent implements OnInit {
+ inputs : Column[] = [];
+
+ predictor?:Predictor ;
+ predictorsForExp: { [expId: string]: Predictor[] } = {}
+ predictorsService: any;
+ experimentsService: any;
+ folders: any;
+ constructor(private predictS : PredictorsService, private route: ActivatedRoute) {
+
+ }
+
+ ngOnInit(): void {
+ /* this.route.params.subscribe(url => {
+ console.log("**********************************************");
+ this.predictS.getPredictor(url["id"]).subscribe(p => {
+
+ this.predictor = p;
+ this.predictor.inputs.forEach((p,index)=> this.inputs[index] = new Column(p, ""));
+ })
+ });
+ }
+
+*/
+}
+
+}
+
+export class Column {
+ constructor(
+ public name : string,
+ public value : (number | string)){
+ }}
diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css
index 456a8df1..f8604125 100644
--- a/frontend/src/app/_elements/graph/graph.component.css
+++ b/frontend/src/app/_elements/graph/graph.component.css
@@ -10,11 +10,14 @@
/*border: 1px solid red;*/
transform: translate(-50%, -50%);
}
-
-.node-text:not(.inputs) {
+.output{
+ font-weight: bold;
+ color: var(--offwhite);
+}
+.node-text:not(.output) {
color: transparent;
}
-.node-text:not(.inputs):hover {
+.node-text:not(.output):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 411e8b53..2ba42eba 100644
--- a/frontend/src/app/_elements/graph/graph.component.html
+++ b/frontend/src/app/_elements/graph/graph.component.html
@@ -1,8 +1,10 @@
<div #graphWrapper class="position-relative" style="height: 14rem; width: 100%;">
- <!-- <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> -->
+ <div *ngIf="outputColumn!='' && inputColumns!=[]">
+ <ng-container *ngFor="let layer of layers; let i = index">
+ <div [ngClass]="{'inputs': i==0,'output':i==layers.length-1}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 100" [style.top.%]="(i!=layers.length-1)?node.y * 100:node.y*70">
+ {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i
+ < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? " "+outputColumn : '')) }} </div>
+ </ng-container>
+ </div>
<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 04be9f04..f14e54e7 100644
--- a/frontend/src/app/_elements/graph/graph.component.ts
+++ b/frontend/src/app/_elements/graph/graph.component.ts
@@ -28,7 +28,8 @@ export class GraphComponent implements AfterViewInit {
@Input() outputNodeColor: string = '#dfd7d7';
private ctx!: CanvasRenderingContext2D;
- @Input() inputColumns?: string[];
+ @Input() inputColumns?: string[]=[];
+ @Input() outputColumn?:string="";
constructor() { }
@@ -81,7 +82,7 @@ export class GraphComponent implements AfterViewInit {
this.layers.push([new Node(outX, outY, this.outputNodeColor)])
this.draw();
}
-
+
draw() {
this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);
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 2ab0a425..139597f9 100644
--- a/frontend/src/app/_elements/metric-view/metric-view.component.html
+++ b/frontend/src/app/_elements/metric-view/metric-view.component.html
@@ -1,21 +1,2 @@
- <!--<div class="container" style="margin:auto;">
- <div class="row">
- <div class="col-xl">
- <div class="demo-content"><app-line-chart-acc [dataAcc]="myAcc" [dataEpoch]=""[dataValAcc]=""></app-line-chart-acc></div>
- </div>
- <div class="col-xl">
- <div class="demo-content"><app-line-chart-loss></app-line-chart-loss></div>
- </div>
- </div>
- <div class="row">
- <div class="col-xl">
- <div class="demo-content"><app-line-chart-mae></app-line-chart-mae></div>
- </div>
- <div class="col-xl">
- <div class="demo-content"><app-line-chart-mse></app-line-chart-mse></div>
- </div>
-</div>
-</div>-->
-<app-line-chart></app-line-chart>
-
+
diff --git a/frontend/src/app/_elements/notifications/notifications.component.html b/frontend/src/app/_elements/notifications/notifications.component.html
index 3b2f4eaa..d3218a96 100644
--- a/frontend/src/app/_elements/notifications/notifications.component.html
+++ b/frontend/src/app/_elements/notifications/notifications.component.html
@@ -1,4 +1,4 @@
-<div *ngIf="notifications && notifications.length > 0" class="position-fixed card card-body p-1 m-3" style="bottom: 0; right: 0; width: 18rem;">
+<div *ngIf="notifications && notifications.length > 0" class="position-fixed card card-body p-1 m-3" style="bottom: 0; right: 0; width: 18rem; z-index: 5000;">
<h2 class="m-auto" (click)="closed = !closed;" data-bs-toggle="collapse" href="#collapseNotifs" role="button" aria-expanded="true" aria-controls="collapseNotifs">
Notifikacije
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.css b/frontend/src/app/_modals/share-dialog/share-dialog.component.css
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.css
diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.html b/frontend/src/app/_modals/share-dialog/share-dialog.component.html
new file mode 100644
index 00000000..32584197
--- /dev/null
+++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.html
@@ -0,0 +1,14 @@
+<h1 mat-dialog-title class="text-center">Podeli {{data.fileType == 0 ? 'izvor podataka' : 'konfiguraciju neuronske mreže'}}</h1>
+<div mat-dialog-content class="mt-5 mb-3 mx-1">
+ <form>
+ <mat-checkbox class="m-2" [(ngModel)]="data.file.isPublic" [ngModelOptions]="{standalone: true}" (change)="publicChanged()">Svi mogu da vide </mat-checkbox>
+ <mat-checkbox class="m-2" [(ngModel)]="data.file.accessibleByLink" [ngModelOptions]="{standalone: true}" (change)="linkChanged()">Osobe sa linkom mogu da vide</mat-checkbox>
+ <div class="input-group my-3">
+ <input type="text" [value]="link" class="form-control" placeholder="" aria-label="Copy link button addon" aria-describedby="button-copy">
+ <button class="btn btn-outline-primary" type="button" id="button-copy" (click)="copy()" [disabled]="!data.file.accessibleByLink"><div class="mt-1"><mat-icon *ngIf="data.file.accessibleByLink">link</mat-icon><mat-icon *ngIf="!data.file.accessibleByLink">link_off</mat-icon></div></button>
+ </div>
+ </form>
+</div>
+<div mat-dialog-actions class="justify-content-center">
+ <button mat-stroked-button (click)="close()">Gotovo</button>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts
new file mode 100644
index 00000000..51bd4c9a
--- /dev/null
+++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ShareDialogComponent } from './share-dialog.component';
+
+describe('ShareDialogComponent', () => {
+ let component: ShareDialogComponent;
+ let fixture: ComponentFixture<ShareDialogComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ShareDialogComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ShareDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts
new file mode 100644
index 00000000..2331cd8b
--- /dev/null
+++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts
@@ -0,0 +1,70 @@
+import { Component, Inject, Input, OnInit } from '@angular/core';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import Dataset from 'src/app/_data/Dataset';
+import { FolderType } from 'src/app/_data/FolderFile';
+import Model from 'src/app/_data/Model';
+import { DatasetsService } from 'src/app/_services/datasets.service';
+import { ModelsService } from 'src/app/_services/models.service';
+
+interface DialogData {
+ file: (Dataset | Model);
+ fileType: FolderType;
+}
+
+@Component({
+ selector: 'app-share-dialog',
+ templateUrl: './share-dialog.component.html',
+ styleUrls: ['./share-dialog.component.css']
+})
+export class ShareDialogComponent implements OnInit {
+ constructor(public dialogRef: MatDialogRef<ShareDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData, private modelsService: ModelsService, private datasetsService: DatasetsService) {
+
+ }
+
+ link: string = '';
+
+ ngOnInit(): void {
+ let link = window.location.origin;
+ if (this.data.fileType == FolderType.Dataset) {
+ link += '/dataset/';
+ } else if (this.data.fileType == FolderType.Model) {
+ link += '/model/';
+ }
+ link += this.data.file._id;
+ this.link = link;
+ }
+
+ close() {
+ this.dialogRef.close();
+ }
+
+ copy() {
+ navigator.clipboard.writeText(this.link);
+ }
+
+ publicChanged() {
+ if (this.data.fileType == FolderType.Dataset) {
+ this.datasetsService.updateDatasetIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => {
+ });
+ } else if (this.data.fileType == FolderType.Model) {
+ this.modelsService.updateModelIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => {
+ });
+ }
+
+ if (this.data.file.isPublic) {
+ this.data.file.accessibleByLink = true;
+ }
+ }
+
+ linkChanged() {
+ if (this.data.fileType == FolderType.Dataset) {
+ this.datasetsService.updateDatasetAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => {
+ });
+ } else if (this.data.fileType == FolderType.Model) {
+ this.modelsService.updateModelAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => {
+ });
+ }
+ }
+
+ FolderType = FolderType;
+}
diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css
new file mode 100644
index 00000000..551a4e21
--- /dev/null
+++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.css
@@ -0,0 +1,21 @@
+#btnYes {
+ background-color: var(--offwhite);
+ color: var(--ns-bg-dark-100);
+}
+
+#btnNo {
+ color: gray;
+}
+
+.wrongInput {
+ color: var(--ns-warn);
+ font-size: 11px;
+}
+
+::ng-deep.mat-dialog-content {
+ overflow: visible;
+}
+
+.hidden-class {
+ opacity: 0;
+} \ No newline at end of file
diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html
new file mode 100644
index 00000000..db44dfe1
--- /dev/null
+++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.html
@@ -0,0 +1,24 @@
+<h1 mat-dialog-title class="text-center">Sačuvaj izmene</h1>
+<div mat-dialog-content class="mt-5 mb-3 mx-1">
+ <form (keydown)="saveWithEnterKey($event)">
+ <mat-radio-group [(ngModel)]="selectedOption" [ngModelOptions]="{standalone: true}">
+ <!-- I Update the existing experiment -->
+ <mat-radio-button [value]="1" checked>Izmeni postojeći eksperiment</mat-radio-button>
+ <!-- II Save as new experiment -->
+ <mat-radio-button [value]="2" class="mt-4">Sačuvaj kao novi eksperiment</mat-radio-button>
+ </mat-radio-group>
+ <div class="d-flex align-items-center justify-content-center mt-3" [ngClass]="{'hidden-class': selectedName.length == 0 && selectedOption == 1}">
+ <mat-form-field [style.width.px]=200>
+ <input type="text" matInput [(ngModel)]="selectedName" (click)="selectedOption = 2" (keydown)="selectedOption = 2" cdkFocusInitial [ngModelOptions]="{standalone: true}" placeholder="Naziv novog eskperimenta">
+ </mat-form-field>
+ </div>
+ <div class="d-flex align-items-center justify-content-center mt-1 mx-4">
+ <p *ngIf="wrongAlreadyExists" class="wrongInput">Eskperiment sa unetim nazivom već postoji u kolekciji.<br>Izaberite drugi naziv.</p>
+ <p *ngIf="wrongEmptyName" class="wrongInput">Unesite naziv eksperimenta.</p>
+ </div>
+ </form>
+</div>
+<div mat-dialog-actions class="justify-content-center">
+ <button id="btnYes" mat-stroked-button color="basic" (click)="onYesClick()">Sačuvaj</button>
+ <button id="btnNo" mat-stroked-button (click)="onNoClick()">Odustani</button>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts
new file mode 100644
index 00000000..e0f26b2c
--- /dev/null
+++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UpdateExperimentDialogComponent } from './update-experiment-dialog.component';
+
+describe('UpdateExperimentDialogComponent', () => {
+ let component: UpdateExperimentDialogComponent;
+ let fixture: ComponentFixture<UpdateExperimentDialogComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ UpdateExperimentDialogComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UpdateExperimentDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts
new file mode 100644
index 00000000..068aa2ef
--- /dev/null
+++ b/frontend/src/app/_modals/update-experiment-dialog/update-experiment-dialog.component.ts
@@ -0,0 +1,68 @@
+import { Component, Inject, OnInit } from '@angular/core';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
+import Experiment from 'src/app/_data/Experiment';
+import { ExperimentsService } from 'src/app/_services/experiments.service';
+
+interface DialogData {
+ experiment: Experiment;
+ selectedOption: number;
+}
+
+@Component({
+ selector: 'app-update-experiment-dialog',
+ templateUrl: './update-experiment-dialog.component.html',
+ styleUrls: ['./update-experiment-dialog.component.css']
+})
+export class UpdateExperimentDialogComponent implements OnInit {
+
+ selectedOption: number = 1;
+ selectedName: string = '';
+ wrongAlreadyExists: boolean = false;
+ wrongEmptyName: boolean = false;
+
+ constructor(public dialogRef: MatDialogRef<UpdateExperimentDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData, private experimentService: ExperimentsService) {
+ this.wrongAlreadyExists = false;
+ this.wrongEmptyName = false;
+ }
+
+ ngOnInit(): void {
+ }
+
+ onNoClick() {
+ this.dialogRef.close();
+ }
+
+ saveWithEnterKey(keyboardEvent: KeyboardEvent) {
+ if (keyboardEvent.code == "Enter" || keyboardEvent.code == "NumpadEnter")
+ this.onYesClick();
+ }
+
+ onYesClick() {
+ if (this.selectedOption == 1) { //update
+ this.experimentService.updateExperiment(this.data.experiment).subscribe((response) => {
+ this.data.experiment = response;
+ this.dialogRef.close(this.data.experiment);
+ });
+ }
+ else { //save new
+ if (this.selectedName == "") {
+ this.wrongEmptyName = true;
+ return;
+ }
+ this.wrongEmptyName = false;
+
+ const newExperiment = new Experiment();
+ Object.assign(newExperiment, this.data.experiment);
+ newExperiment.name = this.selectedName;
+ newExperiment._id = '';
+ this.experimentService.addExperiment(newExperiment!).subscribe((response) => {
+ this.wrongAlreadyExists = false;
+ this.dialogRef.close(response);
+ }, (error) => {
+ if (error.error == "Experiment with this name exists") {
+ this.wrongAlreadyExists = true;
+ }
+ });
+ }
+ }
+}
diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css
index 59e004e9..37edd3c2 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.css
+++ b/frontend/src/app/_pages/experiment/experiment.component.css
@@ -74,4 +74,34 @@ mat-stepper {
.text-overflow-experiment-name {
overflow-wrap: break-word;
+}
+
+#compareButton {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+}
+
+.side-by-side {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ height: 100%;
+}
+
+
+/* one item */
+
+.side-by-side>*:first-child:nth-last-child(1) {
+ /* -or- li:only-child { */
+ width: 100%;
+}
+
+
+/* two items */
+
+.side-by-side>*:first-child:nth-last-child(2),
+.side-by-side>*:first-child:nth-last-child(2)~li {
+ width: 50%;
+ margin: 5px;
} \ No newline at end of file
diff --git a/frontend/src/app/_pages/experiment/experiment.component.html b/frontend/src/app/_pages/experiment/experiment.component.html
index 17a6539d..3176f58c 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.html
+++ b/frontend/src/app/_pages/experiment/experiment.component.html
@@ -25,10 +25,10 @@
<ng-template matStepLabel>
<span class="label addedElement text-overflow" *ngIf="experiment._id!=''">Predvideti:{{experiment.outputColumn}}</span>
<span *ngIf="!this.step1" class="align-middle"><mat-icon>lock</mat-icon></span>
- <span class="label text-overflow" *ngIf="experiment._id==''">Odabir kolona </span>
+ <span class="label text-overflow" *ngIf="experiment._id==''">Priprema podataka </span>
</ng-template>
<ng-template matStepContent>
- <p class="text-left text-overflow">Pripremite podatke i izaberite izlazne kolone</p>
+ <p class="text-left text-overflow">Pripremite podatke i odaberite ulazne i izlaznu kolonu</p>
</ng-template>
</mat-step>
<mat-step [completed]="this.step3">
@@ -57,18 +57,35 @@
</div>
<div #steps id="step_2" class="step-content" *ngIf="step1">
<div class="step-content-inside">
- <app-column-table (okPressed)="goToPage(2); experiment._columnsSelected = true;" (columnTableChanged)="columnTableChangedEvent()" (experimentChanged)="experimentChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table>
+ <app-column-table (columnTableChanged)="columnTableChangedEvent()" (experimentChanged)="experimentChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table>
</div>
</div>
<div #steps id="step_3" class="step-content" *ngIf="step2">
<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 id="compareButton">
+ <button mat-raised-button color="accent" *ngIf="!comparing" (click)="toggleCompare()"><mat-icon>compare</mat-icon> Dodaj konfiguraciju za upoređivanje</button>
+ <button mat-raised-button *ngIf="comparing" (click)="toggleCompare()"><mat-icon>not_interested</mat-icon> Prekini upoređivanje</button>
+ </div>
+ <div class="side-by-side">
+ <app-folder #folderModel [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyModels]" (okPressed)="trainModel();" (selectedFileChanged)="setModel($event);"></app-folder>
+ <app-folder #folderModelCompare [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.MyModels" [tabsToShow]="[TabType.MyModels]" (okPressed)=" trainModelCmp();" (selectedFileChanged)="setModelCmp($event);" style="width: 50%;"
+ *ngIf="comparing"></app-folder>
+ </div>
</div>
</div>
<div #steps id="step_4" class="step-content" *ngIf="step3">
<div class="step-content-inside">
- <app-metric-view #metricView></app-metric-view>
+ <div class="side-by-side">
+ <app-line-chart #linechart [experiment]="experiment" [predictor]="predictor!"></app-line-chart>
+ <app-line-chart #linechartCompare [experiment]="experiment" style="width: 50%;" *ngIf="comparing"></app-line-chart>
+ </div>
</div>
</div>
+ <!--
+ <div #steps id="step_5" class="step-content" *ngIf="step4">
+ <div class="step-content-inside">
+ <app-form-predictor></app-form-predictor>
+ </div>
+ </div>-->
</div>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_pages/experiment/experiment.component.ts b/frontend/src/app/_pages/experiment/experiment.component.ts
index ec4275fa..e4d444b2 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.ts
+++ b/frontend/src/app/_pages/experiment/experiment.component.ts
@@ -15,6 +15,8 @@ import { MetricViewComponent } from 'src/app/_elements/metric-view/metric-view.c
import { ActivatedRoute, Router } from '@angular/router';
import { DatasetsService } from 'src/app/_services/datasets.service';
import { PredictorsService } from 'src/app/_services/predictors.service';
+import { LineChartComponent } from 'src/app/_elements/_charts/line-chart/line-chart.component';
+import Predictor from 'src/app/_data/Predictor';
@Component({
selector: 'app-experiment',
@@ -30,16 +32,32 @@ export class ExperimentComponent implements AfterViewInit {
event: number = 0;
experiment: Experiment;
dataset?: Dataset;
+ predictor?: Predictor;
+
@ViewChild("folderDataset") folderDataset!: FolderComponent;
@ViewChild(ColumnTableComponent) columnTable!: ColumnTableComponent;
@ViewChild("folderModel") folderModel!: FolderComponent;
- @ViewChild("metricView") metricView!: MetricViewComponent;
+ @ViewChild("folderModelCompare") folderModelCmp!: FolderComponent;
+ @ViewChild("linechart") linechartComponent!: LineChartComponent;
+ @ViewChild("linechartCompare") linechartComponentCmp!: LineChartComponent;
step1: boolean = false;
step2: boolean = false;
step3: boolean = false;
step4: boolean = false;
+ comparing: boolean = false;
+
+ toggleCompare() {
+ this.comparing = !this.comparing;
+ setTimeout(() => {
+ if (this.folderModel.formModel)
+ this.folderModel.formModel.graph.resize();
+ if (this.folderModel.formNewModel)
+ this.folderModel.formNewModel.graph.resize();
+ });
+ }
+
constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService, private datasetsService: DatasetsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private route: ActivatedRoute) {
this.experiment = new Experiment("exp1");
}
@@ -57,7 +75,22 @@ export class ExperimentComponent implements AfterViewInit {
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") });
- this.step4 = true;
+ this.step3 = true;
+ setTimeout(() => {
+ this.goToPage(3);
+ });
+ }
+ }
+
+ trainModelCmp() {
+ if (!this.modelToTrainCmp) {
+ Shared.openDialog('Greška', 'Morate odabrati konfiguraciju neuronske mreže');
+ } else {
+ this.modelsService.trainModel(this.modelToTrainCmp._id, this.experiment._id).subscribe(() => { console.log("pocelo treniranje") });
+ this.step3 = true;
+ setTimeout(() => {
+ this.goToPage(3);
+ });
}
}
@@ -83,16 +116,48 @@ export class ExperimentComponent implements AfterViewInit {
if (this.signalRService.hubConnection) {
this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => {
- if (currentEpoch == 0) {
- this.history = [];
- }
+
if (this.modelToTrain?._id == mId) {
+ if (currentEpoch == 0) {
+ this.linechartComponent.setName(mName);
+ this.history = [];
+ }
+
stat = stat.replace(/'/g, '"');
this.history.push(JSON.parse(stat));
- this.metricView.update(this.history,this.modelToTrain.epochs);
+
+ this.linechartComponent.updateAll(this.history, this.modelToTrain.epochs);
+ }
+
+ if (this.modelToTrainCmp?._id == mId) {
+ if (currentEpoch == 0) {
+ this.linechartComponentCmp.setName(mName);
+ this.historyCmp = [];
+ }
+
+ stat = stat.replace(/'/g, '"');
+
+ this.historyCmp.push(JSON.parse(stat));
+ this.linechartComponentCmp.updateAll(this.historyCmp, this.modelToTrainCmp.epochs);
}
});
+ this.signalRService.hubConnection.on("NotifyPredictor", (pId: string, mId: string) => {
+ console.log("Predictor trained: ", pId, "for model:", mId);
+
+ if (this.modelToTrain && mId == this.modelToTrain._id) {
+ this.predictorsService.getPredictor(pId).subscribe((predictor) => {
+ this.linechartComponent.predictor = predictor;
+ });
+ }
+
+ if (this.modelToTrainCmp && mId == this.modelToTrainCmp._id) {
+ this.predictorsService.getPredictor(pId).subscribe((predictor) => {
+ this.linechartComponentCmp.predictor = predictor;
+ });
+ }
+ })
+
}
this.route.queryParams.subscribe(params => {
@@ -102,6 +167,7 @@ export class ExperimentComponent implements AfterViewInit {
if (predictorId != null) {
this.predictorsService.getPredictor(predictorId!).subscribe((response) => {
let predictor = response;
+ this.predictor = predictor;
this.experimentsService.getExperimentById(predictor.experimentId).subscribe((response) => {
this.experiment = response;
this.datasetsService.getDatasetById(this.experiment.datasetId).subscribe((response: Dataset) => {
@@ -111,11 +177,13 @@ export class ExperimentComponent implements AfterViewInit {
this.modelsService.getModelById(predictor.modelId).subscribe((response) => {
let model = response;
- this.folderModel.formModel.newModel = model;
+ this.folderModel.selectFile(model);
+ //this.folderModel.formModel.newModel = model;
this.step3 = true;
- let numOfEpochsArray = Array.from({length: model.epochs}, (_, i) => i + 1);
+ let numOfEpochsArray = Array.from({ length: model.epochs }, (_, i) => i + 1);
setTimeout(() => {
- this.metricView.linechartComponent.update(numOfEpochsArray, predictor.metricsAcc, predictor.metricsLoss, predictor.metricsMae, predictor.metricsMse, predictor.metricsValAcc, predictor.metricsValLoss, predictor.metricsValMae, predictor.metricsValMse);
+ this.linechartComponent.update(numOfEpochsArray, predictor.metricsAcc, predictor.metricsLoss, predictor.metricsMae, predictor.metricsMse, predictor.metricsValAcc, predictor.metricsValLoss, predictor.metricsValMae, predictor.metricsValMse);
+ this.goToPage(3);
})
});
});
@@ -129,6 +197,7 @@ export class ExperimentComponent implements AfterViewInit {
this.dataset = response;
this.folderDataset.forExperiment = this.experiment;
this.folderDataset.selectFile(this.dataset);
+ this.goToPage(1);
});
});
}
@@ -137,6 +206,7 @@ export class ExperimentComponent implements AfterViewInit {
}
history: any[] = [];
+ historyCmp: any[] = [];
updatePageIfScrolled() {
if (this.scrolling) return;
@@ -197,14 +267,18 @@ export class ExperimentComponent implements AfterViewInit {
this.step2 = true;
setTimeout(() => {
this.folderModel.updateExperiment();
+ this.folderModel.selectFile(undefined);
+ this.folderModel.selectTab(TabType.NewFile);
+ this.goToPage(2);
});
}
setDataset(dataset: FolderFile | null) {
- if (dataset == null) {
+ if (dataset == null ||dataset==undefined) {
this.columnTable.loaded = false;
this.dataset = undefined;
this.experiment.datasetId = '';
+ this.step1=false;
return;
}
const d = <Dataset>dataset;
@@ -215,13 +289,21 @@ export class ExperimentComponent implements AfterViewInit {
setTimeout(() => {
this.columnTable.loadDataset(d);
});
+ // REFRESH GRAFIKA (4. KORAKA) URADITI
}
modelToTrain?: Model;
+ modelToTrainCmp?: Model;
setModel(model: FolderFile) {
const m = <Model>model;
this.modelToTrain = m;
- this.step3 = true;
+ //this.step3 = true;
+ }
+
+ setModelCmp(model: FolderFile) {
+ const m = <Model>model;
+ this.modelToTrainCmp = m;
+ //this.step3 = true;
}
}
diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.css b/frontend/src/app/_pages/page-dataset/page-dataset.component.css
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.css
diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.html b/frontend/src/app/_pages/page-dataset/page-dataset.component.html
new file mode 100644
index 00000000..2357a656
--- /dev/null
+++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.html
@@ -0,0 +1,4 @@
+<div class="force-centered" style="color: var(--offwhite);">
+ <app-form-dataset [disableAll]="true"></app-form-dataset>
+ <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts
new file mode 100644
index 00000000..2c961cd5
--- /dev/null
+++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageDatasetComponent } from './page-dataset.component';
+
+describe('PageDatasetComponent', () => {
+ let component: PageDatasetComponent;
+ let fixture: ComponentFixture<PageDatasetComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ PageDatasetComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PageDatasetComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts
new file mode 100644
index 00000000..b6d0c2a4
--- /dev/null
+++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts
@@ -0,0 +1,45 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import Shared from 'src/app/Shared';
+import Dataset from 'src/app/_data/Dataset';
+import { FormDatasetComponent } from 'src/app/_elements/form-dataset/form-dataset.component';
+import { DatasetsService } from 'src/app/_services/datasets.service';
+
+@Component({
+ selector: 'app-page-dataset',
+ templateUrl: './page-dataset.component.html',
+ styleUrls: ['./page-dataset.component.css']
+})
+export class PageDatasetComponent implements OnInit {
+
+ @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent;
+
+ constructor(private route: ActivatedRoute, private router: Router, private datasetsService: DatasetsService) { }
+
+ ngOnInit(): void {
+ this.route.queryParams.subscribe(params => {
+ let id = this.route.snapshot.paramMap.get("id");
+ if (id) {
+ this.datasetsService.getDatasetById(id).subscribe((dataset) => {
+ this.formDataset.dataset = dataset;
+ this.formDataset.loadExisting();
+ });
+ } else {
+ this.router.navigate(['']);
+ }
+ });
+ }
+
+ import() {
+ this.formDataset.dataset._id = "";
+ this.formDataset.dataset.isPreProcess = true;
+ this.formDataset.dataset.isPublic = false;
+ this.datasetsService.stealDataset(this.formDataset.dataset).subscribe((response) => {
+ Shared.openDialog("Obaveštenje", "Uspešno ste dodali javni izvor podataka u vašu kolekciju.");
+ }, (error: any) => {
+ if (error.error == "Dataset with this name already exists") {
+ Shared.openDialog("Obaveštenje", "Izvor podataka sa ovim imenom postoji u vašoj kolekciji.");
+ }
+ });
+ }
+}
diff --git a/frontend/src/app/_pages/page-model/page-model.component.css b/frontend/src/app/_pages/page-model/page-model.component.css
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/frontend/src/app/_pages/page-model/page-model.component.css
diff --git a/frontend/src/app/_pages/page-model/page-model.component.html b/frontend/src/app/_pages/page-model/page-model.component.html
new file mode 100644
index 00000000..0ac8aaae
--- /dev/null
+++ b/frontend/src/app/_pages/page-model/page-model.component.html
@@ -0,0 +1,6 @@
+<div class="force-centered" style="color: var(--offwhite);">
+ <div>
+ {{JSON.stringify(model)}}
+ </div>
+ <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_pages/page-model/page-model.component.spec.ts b/frontend/src/app/_pages/page-model/page-model.component.spec.ts
new file mode 100644
index 00000000..e235de5c
--- /dev/null
+++ b/frontend/src/app/_pages/page-model/page-model.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageModelComponent } from './page-model.component';
+
+describe('PageModelComponent', () => {
+ let component: PageModelComponent;
+ let fixture: ComponentFixture<PageModelComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ PageModelComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(PageModelComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_pages/page-model/page-model.component.ts b/frontend/src/app/_pages/page-model/page-model.component.ts
new file mode 100644
index 00000000..0ccd0f9a
--- /dev/null
+++ b/frontend/src/app/_pages/page-model/page-model.component.ts
@@ -0,0 +1,41 @@
+import { Component, OnInit, ViewChild } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import Shared from 'src/app/Shared';
+import Experiment from 'src/app/_data/Experiment';
+import Model from 'src/app/_data/Model';
+import { FormModelComponent } from 'src/app/_elements/form-model/form-model.component';
+import { ModelsService } from 'src/app/_services/models.service';
+
+@Component({
+ selector: 'app-page-model',
+ templateUrl: './page-model.component.html',
+ styleUrls: ['./page-model.component.css']
+})
+export class PageModelComponent implements OnInit {
+
+ constructor(private route: ActivatedRoute, private router: Router, private modelsService: ModelsService) { }
+ model!:Model;
+ ngOnInit(): void {
+ this.route.queryParams.subscribe(params => {
+ let id = this.route.snapshot.paramMap.get("id");
+ if (id) {
+ this.modelsService.getModelById(id).subscribe((model) => {
+ this.model = model;
+ });
+ } else {
+ this.router.navigate(['']);
+ }
+ });
+ }
+
+ import() {
+ this.model._id = "";
+ this.model.isPublic = false;
+ this.modelsService.stealModel(this.model).subscribe((response) => {
+ Shared.openDialog("Obaveštenje", "Uspešno ste dodali javnu konfiguraciju neuronske mreže u vašu kolekciju.");
+ }, (error: any) => {
+ Shared.openDialog("Obaveštenje", "Konfiguracija neuronske mreže sa ovim imenom postoji u vašoj kolekciji.");
+ });
+ }
+ JSON=JSON;
+}
diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts
index 2775613c..11838b1d 100644
--- a/frontend/src/app/_services/datasets.service.ts
+++ b/frontend/src/app/_services/datasets.service.ts
@@ -1,4 +1,4 @@
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Configuration } from './configuration.service';
@@ -12,6 +12,21 @@ export class DatasetsService {
constructor(private http: HttpClient, private authService: AuthService) { }
+ uploadData(file: File): Observable<any> {
+ let formData = new FormData();
+ formData.append('file', file, file.name);
+
+ let params = new HttpParams();
+
+ const options = {
+ params: params,
+ reportProgress: false,
+ headers: this.authService.authHeader()
+ };
+
+ return this.http.post(`${Configuration.settings.apiURL}/file/csv`, formData, options);
+ }
+
getPublicDatasets(): Observable<Dataset[]> {
return this.http.get<Dataset[]>(`${Configuration.settings.apiURL}/dataset/publicdatasets`, { headers: this.authService.authHeader() });
}
@@ -31,10 +46,10 @@ export class DatasetsService {
getDatasetFile(fileId: any): any {
return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/-1/11`, { headers: this.authService.authHeader(), responseType: 'text' });
}
- getDatasetFilePaging(fileId:any,begin:any,end:any){
+ getDatasetFilePaging(fileId: any, begin: any, end: any) {
return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${begin}/${end}`, { headers: this.authService.authHeader(), responseType: 'text' });
}
- getDatasetHeader(fileId:any){
+ getDatasetHeader(fileId: any) {
return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/-1/1`, { headers: this.authService.authHeader(), responseType: 'text' });
}
getDatasetFilePartial(fileId: any, startRow: number, rowNum: number): Observable<any> {
@@ -44,15 +59,23 @@ export class DatasetsService {
return this.http.get<Dataset>(`${Configuration.settings.apiURL}/dataset/get/${datasetId}`, { headers: this.authService.authHeader() });
}
- editDataset(dataset: Dataset){
- return this.http.put(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader() ,responseType:'text'});
+ editDataset(dataset: Dataset) {
+ return this.http.put(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader(), responseType: 'text' });
}
deleteDataset(dataset: Dataset) {
return this.http.delete(`${Configuration.settings.apiURL}/dataset/` + dataset._id, { headers: this.authService.authHeader(), responseType: "text" });
}
- downloadFile(id:string):Observable<Blob>{
- return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=`+id, { headers: this.authService.authHeader(), responseType: 'blob' });
+ downloadFile(id: string): Observable<Blob> {
+ return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=` + id, { headers: this.authService.authHeader(), responseType: 'blob' });
+ }
+
+ updateDatasetAccessibleByLink(id: string, acessibleByLink: boolean) {
+ return this.http.put(`${Configuration.settings.apiURL}/dataset/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' });
+ }
+
+ updateDatasetIsPublic(id: string, isPublic: boolean) {
+ return this.http.put(`${Configuration.settings.apiURL}/dataset/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' });
}
}
diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts
index 2b8fe8f2..398c7813 100644
--- a/frontend/src/app/_services/models.service.ts
+++ b/frontend/src/app/_services/models.service.ts
@@ -13,21 +13,6 @@ export class ModelsService {
constructor(private http: HttpClient, private authService: AuthService) { }
- uploadData(file: File): Observable<any> {
- let formData = new FormData();
- formData.append('file', file, file.name);
-
- let params = new HttpParams();
-
- const options = {
- params: params,
- reportProgress: false,
- headers: this.authService.authHeader()
- };
-
- return this.http.post(`${Configuration.settings.apiURL}/file/csv`, formData, options);
- }
-
addModel(model: Model): Observable<any> {
return this.http.post(`${Configuration.settings.apiURL}/model/add`, model, { headers: this.authService.authHeader() });
}
@@ -66,4 +51,12 @@ export class ModelsService {
return this.http.get<Model>(`${Configuration.settings.apiURL}/model/byid/${modelId}`, { headers: this.authService.authHeader() });
}
+ updateModelAccessibleByLink(id: string, acessibleByLink: boolean) {
+ return this.http.put(`${Configuration.settings.apiURL}/model/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' });
+ }
+
+ updateModelIsPublic(id: string, isPublic: boolean) {
+ return this.http.put(`${Configuration.settings.apiURL}/model/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' });
+ }
+
}
diff --git a/frontend/src/app/_services/predictors.service.ts b/frontend/src/app/_services/predictors.service.ts
index e2033e3e..5859634d 100644
--- a/frontend/src/app/_services/predictors.service.ts
+++ b/frontend/src/app/_services/predictors.service.ts
@@ -29,6 +29,10 @@ export class PredictorsService {
getMyPredictors(): Observable<Predictor[]> {
return this.http.get<Predictor[]>(`${Configuration.settings.apiURL}/predictor/mypredictors`, { headers: this.authService.authHeader() });
}
+
+ downloadH5(id: string): Observable<Blob> {
+ return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=` + id, { headers: this.authService.authHeader(), responseType: 'blob' });
+ }
}
export class Column {
diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index d5552ce9..fa53d5f0 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -9,16 +9,20 @@ 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';
+import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component';
+import { PageModelComponent } from './_pages/page-model/page-model.component';
const routes: Routes = [
{ path: '', component: HomeComponent, data: { title: 'Početna strana' } },
{ path: 'experiment/p/:predictorId', component: ExperimentComponent, data: { title: 'Eksperiment' } },
{ path: 'experiment/:id', component: ExperimentComponent, data: { title: 'Eksperiment' } },
{ path: 'experiment', component: ExperimentComponent, data: { title: 'Eksperiment' } },
- { path: 'archive', component: ArchiveComponent, data: { title: 'Arhiva' } },
+ { path: 'archive', component: ArchiveComponent, data: { title: 'Kolekcije' } },
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } },
{ path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } },
{ path: 'test', component: TestComponent, data: { title: 'Test' } },
+ { path: 'dataset/:id', component: PageDatasetComponent, data: { title: 'Izvor podataka' } },
+ { path: 'model/:id', component: PageModelComponent, data: { title: 'Konfiguracija neuronske mreže' } },
{ path: '**', redirectTo: '' }
];
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 89d53115..21b09318 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -22,6 +22,7 @@ import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.comp
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';
+import { UpdateExperimentDialogComponent } from './_modals/update-experiment-dialog/update-experiment-dialog.component';
// Pages
import { HomeComponent } from './_pages/home/home.component';
import { ProfileComponent } from './_pages/profile/profile.component';
@@ -53,6 +54,9 @@ import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component'
import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap';
import { MetricViewComponent } from './_elements/metric-view/metric-view.component';
import { SpinnerComponent } from './_elements/spinner/spinner.component';
+import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component';
+import { PageModelComponent } from './_pages/page-model/page-model.component';
+import { ShareDialogComponent } from './_modals/share-dialog/share-dialog.component';
export function initializeApp(appConfig: Configuration) {
return () => appConfig.load();
@@ -93,7 +97,11 @@ export function initializeApp(appConfig: Configuration) {
MetricViewComponent,
LineChartComponent,
SaveExperimentDialogComponent,
- SpinnerComponent
+ SpinnerComponent,
+ UpdateExperimentDialogComponent,
+ PageDatasetComponent,
+ PageModelComponent,
+ ShareDialogComponent
],
imports: [
BrowserModule,