aboutsummaryrefslogtreecommitdiff
path: root/frontend/src/app
diff options
context:
space:
mode:
authorDanijel Anđelković <adanijel99@gmail.com>2022-05-20 04:02:03 +0200
committerDanijel Anđelković <adanijel99@gmail.com>2022-05-20 04:02:03 +0200
commit0d476fb3a73921bbea0994509bc95a19cebae70c (patch)
treea1f2071655b4bd2d78f46c7bb0424a08985664b8 /frontend/src/app
parent60d486a636230074350ac19900125098fd07f3f7 (diff)
parent9930bdb624f9511e9f4ead7abd435d25fbdcac4a (diff)
Merge branch 'redesign' of http://gitlab.pmf.kg.ac.rs/igrannonica/neuronstellar
Diffstat (limited to 'frontend/src/app')
-rw-r--r--frontend/src/app/_data/Dataset.ts3
-rw-r--r--frontend/src/app/_data/Model.ts19
-rw-r--r--frontend/src/app/_data/Predictor.ts19
-rw-r--r--frontend/src/app/_elements/_charts/box-plot/box-plot.component.html2
-rw-r--r--frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts80
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.css4
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.html6
-rw-r--r--frontend/src/app/_elements/_charts/line-chart/line-chart.component.ts65
-rw-r--r--frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html2
-rw-r--r--frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts47
-rw-r--r--frontend/src/app/_elements/column-table/column-table.component.css8
-rw-r--r--frontend/src/app/_elements/column-table/column-table.component.html10
-rw-r--r--frontend/src/app/_elements/column-table/column-table.component.ts242
-rw-r--r--frontend/src/app/_elements/datatable/datatable.component.css4
-rw-r--r--frontend/src/app/_elements/folder/folder.component.css20
-rw-r--r--frontend/src/app/_elements/folder/folder.component.html87
-rw-r--r--frontend/src/app/_elements/folder/folder.component.ts192
-rw-r--r--frontend/src/app/_elements/form-dataset/form-dataset.component.css6
-rw-r--r--frontend/src/app/_elements/form-dataset/form-dataset.component.html71
-rw-r--r--frontend/src/app/_elements/form-dataset/form-dataset.component.ts58
-rw-r--r--frontend/src/app/_elements/form-model/form-model.component.css34
-rw-r--r--frontend/src/app/_elements/form-model/form-model.component.html63
-rw-r--r--frontend/src/app/_elements/form-model/form-model.component.ts82
-rw-r--r--frontend/src/app/_elements/graph/graph.component.html2
-rw-r--r--frontend/src/app/_elements/metric-view/metric-view.component.ts12
-rw-r--r--frontend/src/app/_elements/navbar/navbar.component.html2
-rw-r--r--frontend/src/app/_elements/spinner/spinner.component.css78
-rw-r--r--frontend/src/app/_elements/spinner/spinner.component.html3
-rw-r--r--frontend/src/app/_elements/spinner/spinner.component.spec.ts25
-rw-r--r--frontend/src/app/_elements/spinner/spinner.component.ts15
-rw-r--r--frontend/src/app/_modals/alert-dialog/alert-dialog.component.html16
-rw-r--r--frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts6
-rw-r--r--frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html20
-rw-r--r--frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css4
-rw-r--r--frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html4
-rw-r--r--frontend/src/app/_modals/register-modal/register-modal.component.html2
-rw-r--r--frontend/src/app/_modals/register-modal/register-modal.component.ts21
-rw-r--r--frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.css8
-rw-r--r--frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html12
-rw-r--r--frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.ts9
-rw-r--r--frontend/src/app/_pages/archive/archive.component.css3
-rw-r--r--frontend/src/app/_pages/archive/archive.component.html2
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.css22
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.html93
-rw-r--r--frontend/src/app/_pages/experiment/experiment.component.ts81
-rw-r--r--frontend/src/app/_pages/home/home.component.css10
-rw-r--r--frontend/src/app/_pages/home/home.component.html8
-rw-r--r--frontend/src/app/_pages/my-models/my-models.component.html43
-rw-r--r--frontend/src/app/_pages/my-models/my-models.component.ts59
-rw-r--r--frontend/src/app/_pages/profile/profile.component.css5
-rw-r--r--frontend/src/app/_pages/profile/profile.component.html8
-rw-r--r--frontend/src/app/_pages/profile/profile.component.ts46
-rw-r--r--frontend/src/app/_services/datasets.service.ts21
-rw-r--r--frontend/src/app/_services/experiments.service.ts8
-rw-r--r--frontend/src/app/_services/models.service.ts10
-rw-r--r--frontend/src/app/app-routing.module.ts6
-rw-r--r--frontend/src/app/app.component.ts114
-rw-r--r--frontend/src/app/app.module.ts4
58 files changed, 1335 insertions, 571 deletions
diff --git a/frontend/src/app/_data/Dataset.ts b/frontend/src/app/_data/Dataset.ts
index a962fe6b..c8d5771a 100644
--- a/frontend/src/app/_data/Dataset.ts
+++ b/frontend/src/app/_data/Dataset.ts
@@ -4,7 +4,7 @@ export default class Dataset extends FolderFile {
constructor(
name: string = 'Novi izvor podataka',
public description: string = '',
- public fileId?: number,
+ public fileId?: string,
public extension: string = '.csv',
public isPublic: boolean = false,
public accessibleByLink: boolean = false,
@@ -17,6 +17,7 @@ export default class Dataset extends FolderFile {
public rowCount: number = 0,
public nullRows: number = 0,
public nullCols: number = 0,
+ public isPreProcess : Boolean = false,
public cMatrix: number[][] = []
) {
super(name, dateCreated, lastUpdated);
diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts
index cc25c91b..898805d3 100644
--- a/frontend/src/app/_data/Model.ts
+++ b/frontend/src/app/_data/Model.ts
@@ -2,6 +2,7 @@ import { NgIf } from "@angular/common";
import { FolderFile } from "./FolderFile";
export default class Model extends FolderFile {
+ public lossFunction: LossFunction;
constructor(
name: string = 'Novi model',
public description: string = '',
@@ -12,7 +13,6 @@ export default class Model extends FolderFile {
// Neural net training settings
public type: ProblemType = ProblemType.Regression,
public optimizer: Optimizer = Optimizer.Adam,
- public lossFunction: LossFunction = LossFunctionRegression[0],
public inputNeurons: number = 1,
public hiddenLayers: number = 1,
public batchSize: BatchSize = BatchSize.O3,
@@ -21,18 +21,19 @@ export default class Model extends FolderFile {
public metrics: string[] = [], // TODO add to add-model form
public epochs: number = 5, // TODO add to add-model form
public inputColNum: number = 5,
- public learningRate: LearningRate = LearningRate.LR1,
+ public learningRate: LearningRate = LearningRate.LR3,
public layers: Layer[] = [new Layer()],
// Test set settings
public randomOrder: boolean = true,
public randomTestSet: boolean = true,
public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U
-
+ public validationSize: number = 0.1,
public isPublic: boolean = false,
public accessibleByLink: boolean = false
) {
super(name, dateCreated, lastUpdated);
+ this.lossFunction = (this.type == ProblemType.Regression ? LossFunctionRegression[0] : (this.type == ProblemType.BinaryClassification ? LossFunctionBinaryClassification[0] : LossFunctionMultiClassification[0]));
}
}
export class Layer {
@@ -45,8 +46,8 @@ export class Layer {
) { }
}
export enum LearningRate {
- LR1 = '0.00001',
- LR2 = '0.0001',
+ // LR1 = '0.00001',
+ // LR2 = '0.0001',
LR3 = '0.001',
LR4 = '0.003',
LR5 = '0.01',
@@ -117,10 +118,10 @@ export enum ActivationFunctionOutputLayer
export enum LossFunction {
// binary classification loss functions
BinaryCrossEntropy = 'binary_crossentropy',
- SquaredHingeLoss = 'squared_hinge_loss',
- HingeLoss = 'hinge_loss',
+ SquaredHingeLoss = 'squared_hinge',
+ HingeLoss = 'hinge',
// multi-class classification loss functions
- CategoricalCrossEntropy = 'categorical_crossentropy',
+ // CategoricalCrossEntropy = 'categorical_crossentropy',
SparseCategoricalCrossEntropy = 'sparse_categorical_crossentropy',
KLDivergence = 'kullback_leibler_divergence',
@@ -134,7 +135,7 @@ export enum LossFunction {
export const LossFunctionRegression = [LossFunction.MeanAbsoluteError, LossFunction.MeanSquaredError, LossFunction.MeanSquaredLogarithmicError]
export const LossFunctionBinaryClassification = [LossFunction.BinaryCrossEntropy, LossFunction.SquaredHingeLoss, LossFunction.HingeLoss]
-export const LossFunctionMultiClassification = [LossFunction.CategoricalCrossEntropy, LossFunction.SparseCategoricalCrossEntropy, LossFunction.KLDivergence]
+export const LossFunctionMultiClassification = [/*LossFunction.CategoricalCrossEntropy,*/ LossFunction.SparseCategoricalCrossEntropy, LossFunction.KLDivergence]
export enum Optimizer {
Adam = 'Adam',
diff --git a/frontend/src/app/_data/Predictor.ts b/frontend/src/app/_data/Predictor.ts
index 55d610ed..8de4cd17 100644
--- a/frontend/src/app/_data/Predictor.ts
+++ b/frontend/src/app/_data/Predictor.ts
@@ -3,19 +3,28 @@ import { FolderFile } from "./FolderFile";
export default class Predictor extends FolderFile {
constructor(
name: string = 'Novi prediktor',
- public description: string = '',
+
+ public uploaderId: string = '',
public inputs: string[] = [],
public output: string = '',
public isPublic: boolean = false,
public accessibleByLink: boolean = false,
dateCreated: Date = new Date(),
- lastUpdated: Date = new Date(),
- public uploaderId: string = '',
- //public finalMetrics: Metric[] = []
public experimentId: string = "",
public modelId: string = "",
+ public h5FileId: string = "",
+ public metricsLoss:number[]=[],
+ public metricsValLoss:number []=[],
+ public metricsAcc:number[]=[],
+ public metricsValAcc: number[]=[],
+ public metricsMae :number []=[],
+ public metricsValMae :number []=[],
+ public metricsMse : number[]=[],
+ public metricsValMse : number[]=[],
+ //public metrics: Metric[] = [],
+ //public finalMetrics: Metric[] = []
) {
- super(name, dateCreated, lastUpdated);
+ super(name, dateCreated, dateCreated);
}
}
diff --git a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html
index 688eafae..3b2bf976 100644
--- a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html
+++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.html
@@ -1,3 +1,3 @@
-<div class="chart-wrapper">
+<div class="chart-wrapper position-relative">
<canvas #boxplot [width]="width" [height]="height"></canvas>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts
index bf5e3fd6..b3d25280 100644
--- a/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts
+++ b/frontend/src/app/_elements/_charts/box-plot/box-plot.component.ts
@@ -16,48 +16,59 @@ Chart.register(BoxPlotController, BoxAndWiskers, LinearScale, CategoryScale);
})
export class BoxPlotComponent implements AfterViewInit {
- @Input() width?: number;
- @Input() height?: number;
- @Input() mean?: number;
- @Input() median?: number;
- @Input() min?: number;
- @Input() max?: number;
- @Input() q1?: number;
- @Input() q3?: number;
+ @Input() width!: number;
+ @Input() height!: number;
+ @Input() mean!: number;
+ @Input() median!: number;
+ @Input() min!: number;
+ @Input() max!: number;
+ @Input() q1!: number;
+ @Input() q3!: number;
- updateChart(min: number, max: number, q1: number, q3: number, median: number) {
- if (this.myChart) {
- this.boxplotData.datasets[0].data = [[min, q1, median, q3, max]]
- this.myChart.update();
- }
- };
+
+
+ /*
+ updatePieChart(uniqueValues: string[], uniqueValuesPercent: number[]){
+ //console.log(this.uniqueValues, this.uniqueValuesPercent);
+ this.pieChartData.datasets = [{
+ label: "%",
+ backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850", "#000000"],
+ data: uniqueValuesPercent,
+ }];
+ this.pieChartData.labels = uniqueValues
+ console.log(this.uniqueValues, this.uniqueValuesPercent);
+ this.myChart?.update()
+ };
+ */
@ViewChild('boxplot') chartRef!: ElementRef;
constructor() {
//this.updateChart();
}
- boxplotData = {
- // define label tree
- //labels: ['January'/*, 'February', 'March', 'April', 'May', 'June', 'July'*/],
- datasets: [{
- label: 'Dataset 1',
- backgroundColor: '#0063AB',
- borderColor: '#dfd7d7',
- borderWidth: 1,
- outlierColor: '#999999',
- scaleFontColor: '#0063AB',
- padding: 10,
- itemRadius: 0,
- data: [
- randomValues(100, 0, 100),
- ]
- }]
- };
+
ngAfterViewInit(): void {
- this.myChart = new Chart(this.chartRef.nativeElement, {
+ const boxplotData = {
+ // define label tree
+ //labels: ['January'/*, 'February', 'March', 'April', 'May', 'June', 'July'*/],
+ labels:[""],
+ datasets: [{
+ label: 'Dataset 1',
+ backgroundColor: '#0063AB',
+ borderColor: '#dfd7d7',
+ borderWidth: 1,
+ outlierColor: '#999999',
+ scaleFontColor: '#0063AB',
+ padding: 10,
+ itemRadius: 0,
+ data: [
+ {min:this.min,q1:this.q1,q3:this.q3,median:this.median,max:this.max,mean:this.mean}
+ ]
+ }]
+ };
+ const myChart = new Chart(this.chartRef.nativeElement, {
type: "boxplot",
- data: this.boxplotData,
+ data: boxplotData,
options: {
plugins: {
legend: {
@@ -74,8 +85,6 @@ export class BoxPlotComponent implements AfterViewInit {
}
},
y: {
- min: this.min,
- max: this.max,
ticks: {
color: '#dfd7d7'
},
@@ -88,5 +97,4 @@ export class BoxPlotComponent implements AfterViewInit {
});
}
- myChart?: Chart;
}
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 a190693a..2eea561e 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
@@ -6,4 +6,6 @@ canvas{
background-color: var(--ns-bg-dark-100);
border-radius: 5px;
margin: 10px;
- } \ No newline at end of file
+ font-size: 11 !important;
+ }
+ \ 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 5bb7aae6..1c711562 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,4 @@
-
- <canvas id="myChart" >
+<div #wrapper class="position-relative" style="width:100%;height:95%;">
+ <canvas id="myChart" #canvas>
</canvas>
- \ No newline at end of file
+</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 e873618c..7d21129c 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,4 +1,4 @@
-import { Component, AfterViewInit, ViewChild } from '@angular/core';
+import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core';
import { Chart } from 'chart.js';
@Component({
@@ -19,43 +19,63 @@ export class LineChartComponent implements AfterViewInit {
dataValLoss:number[]=[];
dataEpoch: number[] = [];
+ @ViewChild('wrapper')
+ wrapper!: ElementRef;
+ @ViewChild('canvas')
+ canvas!: ElementRef;
+
constructor() {
+
}
-
+ width = 700;
+ height = 400;
+
myChart!: Chart;
-
+ resize() {
+ 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[]) {
- this.dataAcc.length = 0;
- this.dataAcc.push(...myAcc);
-
+
this.dataEpoch.length = 0;
this.dataEpoch.push(...myEpochs);
- this.dataMAE.length = 0;
- this.dataMAE.push(...myMae);
+ this.dataAcc.length = 0;
+ this.dataAcc.push(...myAcc);
this.dataLOSS.length = 0;
this.dataLOSS.push(...myLoss);
- this.dataMSE.length = 0;
- this.dataMSE.push(...myValAcc);
+ this.dataMAE.length = 0;
+ this.dataMAE.push(...myMae);
this.dataMSE.length = 0;
- this.dataMSE.push(...myValLoss);
+ this.dataMSE.push(...myMse);
- this.dataMSE.length = 0;
- this.dataMSE.push(...myValMae);
+ this.dataValAcc.length = 0;
+ this.dataValAcc.push(...myValAcc);
- this.dataMSE.length = 0;
- this.dataMSE.push(...myValMse);
+ this.dataValLoss.length = 0;
+ this.dataValLoss.push(...myValLoss);
- this.dataMSE.length = 0;
- this.dataMSE.push(...myMse);
+ this.dataValMAE.length = 0;
+ this.dataValMAE.push(...myValMae);
+
+ this.dataValMSE.length = 0;
+ this.dataValMSE.push(...myValMse);
this.myChart.update();
}
ngAfterViewInit(): void {
+
+ window.addEventListener('resize', () => { this.resize() });
+ this.resize();
this.myChart = new Chart("myChart",
{
type: 'line',
@@ -69,8 +89,8 @@ export class LineChartComponent implements AfterViewInit {
},
{
- label: 'VAl_Accuracy',
- data: this.dataMSE,
+ label: 'Val_Accuracy',
+ data: this.dataValAcc,
borderWidth: 1
},
{
@@ -80,7 +100,7 @@ export class LineChartComponent implements AfterViewInit {
},
{
label: 'Val_Loss',
- data: this.dataMSE,
+ data: this.dataValLoss,
borderWidth: 1
},
{
@@ -90,7 +110,7 @@ export class LineChartComponent implements AfterViewInit {
},
{
label: 'Val_MAE',
- data: this.dataMSE,
+ data: this.dataValMAE,
borderWidth: 1
},
{
@@ -100,7 +120,7 @@ export class LineChartComponent implements AfterViewInit {
},
{
label: 'Val_MSE',
- data: this.dataMSE,
+ data: this.dataValMSE,
borderWidth: 1
}
]
@@ -150,3 +170,4 @@ export class LineChartComponent implements AfterViewInit {
);
}
}
+
diff --git a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html
index 7faf3af0..fe3998ff 100644
--- a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html
+++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.html
@@ -1,3 +1,3 @@
-<div class="chart-wrapper">
+<div class="chart-wrapper position-relative">
<canvas #piechart [width]="width" [height]="height"></canvas>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts
index c2bd3262..9264e41c 100644
--- a/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts
+++ b/frontend/src/app/_elements/_charts/pie-chart/pie-chart.component.ts
@@ -14,16 +14,16 @@ export class PieChartComponent implements AfterViewInit {
@Input()uniqueValuesPercent?: number[] = [];
updatePieChart(uniqueValues: string[], uniqueValuesPercent: number[]){
- console.log(this.uniqueValues, this.uniqueValuesPercent);
- const newPieChartData = {
- datasets: [{
- label: "Population (millions)",
- backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"],
- data: [2478,5267,734,784,433],
- }]
-
- }
- };
+ //console.log(this.uniqueValues, this.uniqueValuesPercent);
+ this.pieChartData.datasets = [{
+ label: "%",
+ backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850", "#000000"],
+ data: uniqueValuesPercent,
+ }];
+ this.pieChartData.labels = uniqueValues
+ console.log(this.uniqueValues, this.uniqueValuesPercent);
+ this.myChart?.update()
+ };
@ViewChild('piechart') chartRef!: ElementRef;
constructor() { }
@@ -33,18 +33,31 @@ export class PieChartComponent implements AfterViewInit {
label: "Population (millions)",
backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"],
data: [2478,5267,734,784,433]
- }]
+ }], labels : [""]
}
+ myChart?: Chart;
ngAfterViewInit(): void {
- const myChart = new Chart(this.chartRef.nativeElement, {
+ let rem = 100;
+ const percents : number[] = []
+ this.uniqueValuesPercent?.forEach(percent => {
+ rem-=percent*100;
+ percents.push(percent*100)
+
+ })
+ const data = {
+ datasets: [{
+ label: "%",
+ backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850", "#000000"],
+ data: [...percents, rem]
+ }], labels : [...this.uniqueValues!,"Ostalo"]
+ }
+
+ const myChart = new Chart(this.chartRef.nativeElement, {
type: 'pie',
- data: this.pieChartData,
+ data: data
+ ,
options: {
- /*title: {
- display: true,
- text: 'Predicted world population (millions) in 2050'
- }*/
plugins:{
legend: {
display: false
diff --git a/frontend/src/app/_elements/column-table/column-table.component.css b/frontend/src/app/_elements/column-table/column-table.component.css
index 0477b7be..40ae3c05 100644
--- a/frontend/src/app/_elements/column-table/column-table.component.css
+++ b/frontend/src/app/_elements/column-table/column-table.component.css
@@ -13,7 +13,7 @@ table.fixed {
}
table.fixed td {
- overflow: hidden;
+ /* overflow: hidden; */
max-width: 200px;
min-width: 200px;
vertical-align: middle;
@@ -21,8 +21,8 @@ table.fixed td {
}
table.fixed th {
- overflow: hidden;
- max-width: 120px;
+ /* overflow: hidden; */
+ max-width: 250px;
min-width: 120px;
vertical-align: middle;
background-color: var(--ns-primary-50);
@@ -198,7 +198,7 @@ table ::ng-deep .mat-form-field-wrapper {
font-size: large;
position: relative;
background-color: var(--ns-primary);
- width: 10rem;
+ width: 13rem;
height: 2.3rem;
border-color: var(--ns-primary);
border-style: solid;
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 d07d50b2..e6a781be 100644
--- a/frontend/src/app/_elements/column-table/column-table.component.html
+++ b/frontend/src/app/_elements/column-table/column-table.component.html
@@ -35,7 +35,7 @@
</thead>
<tbody>
<tr *ngFor="let row of tableData; let i = index">
- <th>#{{i}}</th>
+ <th>#{{((this.begin/10)+1)*10-10+i+1}}</th>
<td *ngFor="let col of row; let j = index" [ngClass]="{'text-disabled' : !columnsChecked[j]}">
<div class="text-overflow">
{{col}}
@@ -44,6 +44,12 @@
</tr>
</tbody>
</table>
+ <div class="mb-3">
+ <button mat-button (click)="goBack()"><mat-icon>keyboard_arrow_left</mat-icon></button>
+ <div style="display: inline;">{{(this.begin/10)+1}}...{{getPage()}}</div>
+ <button mat-button (click)="goForward()"><mat-icon>keyboard_arrow_right</mat-icon></button>
+
+ </div>
</div>
<div [ngClass]="{'hidden': tabToDisplay != Table.CorrelationMatrix}">
@@ -211,7 +217,7 @@
<div class="ns-col rounded">
<mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100">
<mat-label>Izlazna kolona</mat-label>
- <mat-select [(value)]="experiment.outputColumn" (selectionChange)="changeProblemType()">
+ <mat-select [(value)]="experiment.outputColumn" (selectionChange)="outputColumnChanged()">
<mat-option *ngFor="let inputColumn of experiment.inputColumns" [value]="inputColumn">{{inputColumn}}</mat-option>
<mat-option *ngIf="experiment.inputColumns.length == 0" value="-">-</mat-option>
</mat-select>
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 217eda30..a9057695 100644
--- a/frontend/src/app/_elements/column-table/column-table.component.ts
+++ b/frontend/src/app/_elements/column-table/column-table.component.ts
@@ -14,6 +14,7 @@ import { AlertDialogComponent } from 'src/app/_modals/alert-dialog/alert-dialog.
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';
@Component({
selector: 'app-column-table',
@@ -41,70 +42,100 @@ export class ColumnTableComponent implements AfterViewInit {
columnsChecked: boolean[] = []; //niz svih kolona
loaded: boolean = false;
+ begin:number=0;
+ step:number=10;
- constructor(private datasetService: DatasetsService, private experimentService: ExperimentsService, public csvParseService: CsvParseService, public dialog: MatDialog) {
- //ovo mi nece trebati jer primam dataset iz druge komponente
- }
- updateCharts() {
- //min: number, max: number, q1: number, q3: number, median: number
- let i = 0;
- this.boxplotComp.changes.subscribe(() => {
- const bps = this.boxplotComp.toArray();
- this.dataset?.columnInfo.forEach(colInfo => {
- if (this.experiment.columnTypes[i] == ColumnType.numerical) {
- bps[i].updateChart(colInfo!.min, colInfo.max, colInfo.q1, colInfo.q3, colInfo.median);
- i++;
- }
- });
- });
+
+ constructor(private datasetService: DatasetsService, private experimentService: ExperimentsService, public csvParseService: CsvParseService, public dialog: MatDialog, private route: ActivatedRoute) {
}
+ resetPagging(){
+ this.begin=0;
+ }
+ goBack(){
+ if(this.begin-10<0)
+ this.begin=0;
+ else
+ {
+ this.begin-=10;
+ this.loadData();
+ }
- updatePieChart() {
- //min: number, max: number, q1: number, q3: number, median: number
- let i = 0;
- const pieChart = this.piechartComp.toArray();
- this.dataset?.columnInfo.forEach(colInfo => {
- if (this.experiment.columnTypes[i] == ColumnType.categorical) {
- pieChart[i].updatePieChart(colInfo!.uniqueValues, colInfo.uniqueValuesPercent);
- i++;
- }
- });
+ }
+ goForward(){
+ if(this.dataset!=undefined){
+ this.begin+=10;
+ if(this.dataset.rowCount<this.begin)
+ this.begin-=10;
+ else
+ this.loadData();
+ }
+ }
+ getPage(){
+ if(this.dataset!=undefined)
+ return Math.ceil(this.dataset.rowCount/this.step);
+ return 0;
}
- loadDataset(dataset: Dataset) {
- console.log("LOADED DATASET");
- this.dataset = dataset;
- this.setColumnTypeInitial();
- this.dataset.columnInfo.forEach(column => {
- this.columnsChecked.push(true);
- });
+
- this.resetInputColumns();
- this.resetOutputColumn();
- this.resetColumnEncodings(Encoding.Label);
- this.setDeleteRowsForMissingValTreatment();
- this.nullValOption = [];
- this.dataset.columnInfo.forEach(colInfo => {
- this.nullValOption.push(`Obriši redove (${colInfo.numNulls})`);
- });
- this.datasetService.getDatasetFilePartial(this.dataset.fileId, 0, 10).subscribe((response: string | undefined) => {
+ loadDataset(dataset: Dataset) {
+ console.log("LOADED DATASET");
+
+ if (this.route.snapshot.paramMap.get("id") == null && this.route.snapshot.paramMap.get("predictorId") == null) {
+ this.dataset = dataset;
+ this.setColumnTypeInitial();
+
+ this.columnsChecked = [];
+ this.dataset.columnInfo.forEach(column => {
+ this.columnsChecked.push(true);
+ });
+
+ this.resetInputColumns();
+ this.resetOutputColumn();
+ this.resetColumnEncodings(Encoding.Label);
+ this.setDeleteRowsForMissingValTreatment();
+
+ this.nullValOption = [];
+ this.dataset.columnInfo.forEach(colInfo => {
+ this.nullValOption.push(`Obriši redove (${colInfo.numNulls})`);
+ });
+ }
+ else {
+ this.dataset = dataset;
+ this.experimentChanged.emit();
+ this.columnsChecked = [];
+ this.dataset.columnInfo.forEach(column => {
+ if (this.experiment.inputColumns.find(x => x == column.columnName) != undefined)
+ this.columnsChecked.push(true);
+ else
+ this.columnsChecked.push(false);
+ });
+ //this.nullValOption = [];
+ for (let i = 0; i < this.dataset!.columnInfo.length; i++) {
+ //let nullValRep = this.experiment.nullValuesReplacers.find(x => x.column == this.dataset!.columnInfo[i].columnName);
+ let nullValRep = this.experiment.nullValuesReplacers[i];
+ this.nullValOption[i] = (nullValRep.option == NullValueOptions.DeleteRows) ? `Obriši redove (${this.dataset.columnInfo[i].numNulls})` : ((nullValRep.option == NullValueOptions.DeleteColumns) ? `Isključi kolonu` : `Popuni sa ${nullValRep.value}`);
+ }
+ }
+ this.resetPagging();
+ this.loadData();
+ this.loaded = true;
+ }
+
+ loadData(){
+ if(this.dataset!=undefined)
+ this.datasetService.getDatasetFilePartial(this.dataset.fileId, this.begin, this.step).subscribe((response: string | undefined) => {
if (response && this.dataset != undefined) {
this.tableData = this.csvParseService.csvToArray(response, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter);
}
});
- this.loaded = true;
-
- this.updateCharts();
- this.updatePieChart();
}
ngAfterViewInit(): void {
- console.log(this.dataset?.columnInfo);
-
}
setColumnTypeInitial() {
@@ -124,17 +155,19 @@ export class ColumnTableComponent implements AfterViewInit {
}
}
resetOutputColumn() {
- if (this.experiment.inputColumns.length > 0)
+ if (this.experiment.inputColumns.length > 0) {
this.experiment.outputColumn = this.experiment.inputColumns[0];
+ this.changeProblemType();
+ }
else
this.experiment.outputColumn = '-';
}
setDeleteRowsForMissingValTreatment() {
- if (this.experiment != undefined) {
+ if (this.experiment != undefined && this.dataset != undefined) {
this.experiment.nullValues = NullValueOptions.DeleteRows;
this.experiment.nullValuesReplacers = [];
- for (let i = 0; i < this.experiment.inputColumns.length; i++) {
+ for (let i = 0; i < this.dataset?.columnInfo.length; i++) {
this.experiment.nullValuesReplacers.push({
column: this.experiment.inputColumns[i],
option: NullValueOptions.DeleteRows,
@@ -164,13 +197,17 @@ export class ColumnTableComponent implements AfterViewInit {
}
if (this.experiment.inputColumns.length == 1)
this.experiment.outputColumn = this.experiment.inputColumns[0];
+
+ let index = this.dataset?.columnInfo.findIndex(x => x.columnName == columnName)!;
+ this.nullValOption[index] = (this.experiment.nullValuesReplacers[index].option == NullValueOptions.DeleteRows) ? "Obriši redove (" + this.dataset?.columnInfo[index].numNulls + ")" : "Popuni sa " + this.experiment.nullValuesReplacers[index].value;
}
else {
this.experiment.inputColumns = this.experiment.inputColumns.filter(x => x != columnName);
//console.log("Input columns: ", this.experiment.inputColumns);
//TODO: da se zatamni kolona koja je unchecked
//this.experiment.encodings = this.experiment.encodings.filter(x => x.columnName != columnName); samo na kraju iz enkodinga skloni necekirane
- this.experiment.nullValuesReplacers = this.experiment.nullValuesReplacers.filter(x => x.column != columnName);
+ let index = this.dataset?.columnInfo.findIndex(x => x.columnName == columnName)!;
+ this.nullValOption[index] = "Isključi kolonu";
if (columnName == this.experiment.outputColumn) {
if (this.experiment.inputColumns.length > 0)
this.experiment.outputColumn = this.experiment.inputColumns[0];
@@ -182,17 +219,37 @@ export class ColumnTableComponent implements AfterViewInit {
}
}
+ outputColumnChanged() {
+ let outputColReplacer = this.experiment.nullValuesReplacers.find(x => x.column == this.experiment.outputColumn);
+ //let index = this.experiment.nullValuesReplacers.findIndex(x => x.column == this.experiment.outputColumn);
+ if (outputColReplacer != undefined) {
+ outputColReplacer.option = NullValueOptions.DeleteRows;
+
+ let numOfRowsToDelete = (this.dataset!.columnInfo.filter(x => x.columnName == this.experiment.outputColumn)[0]).numNulls;
+ let index = this.dataset!.columnInfo.findIndex(x => x.columnName == this.experiment.outputColumn);
+ this.nullValOption[index] = "Obriši redove (" + numOfRowsToDelete + ")";
+ }
+
+ this.changeProblemType();
+ }
+
changeProblemType() {
if (this.experiment != undefined && this.dataset != undefined) {
+ //console.log(this.experiment.outputColumn);
let i = this.dataset.columnInfo.findIndex(x => x.columnName == this.experiment!.outputColumn);
if (i == -1 || this.experiment.columnTypes[i] == ColumnType.numerical) {
+ //console.log("USAO U REGRESIONI");
this.experiment.type = ProblemType.Regression;
}
else {
- if (this.dataset.columnInfo[i].uniqueValues!.length == 2)
+ if (this.dataset.columnInfo[i].uniqueValues!.length == 2) {
+ //console.log("USAO U BINARY");
this.experiment.type = ProblemType.BinaryClassification;
- else
+ }
+ else {
+ //console.log("USAO U multi");
this.experiment.type = ProblemType.MultiClassification;
+ }
}
this.columnTableChangeDetected();
}
@@ -232,16 +289,47 @@ export class ColumnTableComponent implements AfterViewInit {
if (this.experiment != undefined && this.dataset != undefined) {
if (selectedMissingValuesOption == NullValueOptions.DeleteColumns) {
- this.experiment.nullValues = NullValueOptions.DeleteColumns;
+ for (let i = 0; i < this.dataset.columnInfo.length; i++) {
+ if (this.dataset.columnInfo[i].numNulls > 0 && this.dataset.columnInfo[i].columnName != this.experiment.outputColumn) {
+ this.experiment.inputColumns = this.experiment.inputColumns.filter(x => x != this.dataset!.columnInfo[i].columnName);
+ this.columnsChecked[i] = false;
+ console.log(this.dataset!.columnInfo[i].columnName);
+
+ this.nullValOption[i] = "Isključi kolonu";
+ }
+ }
+ /*this.experiment.nullValues = NullValueOptions.DeleteColumns;
+
+ let outputColReplacer = this.experiment.nullValuesReplacers.find(x => x.column == this.experiment.outputColumn);
+
this.experiment.nullValuesReplacers = [];
for (let i = 0; i < this.experiment.inputColumns.length; i++) {
- this.experiment.nullValuesReplacers.push({
- column: this.experiment.inputColumns[i],
- option: NullValueOptions.DeleteColumns,
- value: ""
- });
- this.nullValOption[i] = "Obriši kolonu";
- }
+ if (this.experiment.inputColumns[i] != this.experiment.outputColumn) {
+ this.experiment.nullValuesReplacers.push({ //ovo zakomentarisano
+ column: this.experiment.inputColumns[i],
+ option: NullValueOptions.DeleteColumns,
+ value: ""
+ });
+ let colIndex = this.dataset.columnInfo.findIndex(x => x.columnName == this.experiment.inputColumns[i]);
+ this.nullValOption[colIndex] = "Isključi kolonu";
+ }
+ else {
+ if (outputColReplacer != undefined) {
+ this.experiment.nullValuesReplacers.push(outputColReplacer);
+ let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[i])[0]).numNulls;
+ let colIndex = this.dataset.columnInfo.findIndex(x => x.columnName == this.experiment.inputColumns[i]);
+ this.nullValOption[colIndex] = (outputColReplacer.option == NullValueOptions.DeleteRows) ? "Obriši redove (" + numOfRowsToDelete + ")" : "Popuni sa " + outputColReplacer.value + "";
+ }
+ }
+ }*/
+ //obrisi kolone koje sadrze nedostajuce vrednosti iz input kolona
+ /*for (let i = 0; i < this.dataset.columnInfo.length; i++) {
+ if (this.dataset.columnInfo[i].numNulls > 0) {
+ this.experiment.inputColumns = this.experiment.inputColumns.filter(x => x != this.dataset!.columnInfo[i].columnName);
+ this.columnsChecked[i] = false;
+ console.log(this.dataset!.columnInfo[i].columnName);
+ }
+ }*/
}
else if (selectedMissingValuesOption == NullValueOptions.DeleteRows) {
this.experiment.nullValues = NullValueOptions.DeleteRows;
@@ -253,7 +341,8 @@ export class ColumnTableComponent implements AfterViewInit {
value: ""
});
let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[i])[0]).numNulls;
- this.nullValOption[i] = "Obriši redove (" + numOfRowsToDelete + ")";
+ let colIndex = this.dataset.columnInfo.findIndex(x => x.columnName == this.experiment.inputColumns[i]);
+ this.nullValOption[colIndex] = "Obriši redove (" + numOfRowsToDelete + ")";
}
}
this.columnTableChangeDetected();
@@ -261,7 +350,8 @@ export class ColumnTableComponent implements AfterViewInit {
}
openMissingValuesDialog() {
const dialogRef = this.dialog.open(MissingvaluesDialogComponent, {
- width: '400px'
+ width: '500px',
+ panelClass: 'custom-modalbox'
});
dialogRef.afterClosed().subscribe(selectedMissingValuesOption => {
if (selectedMissingValuesOption != undefined)
@@ -295,7 +385,16 @@ export class ColumnTableComponent implements AfterViewInit {
MissValsDeleteClicked(event: Event, replacementType: NullValueOptions, index: number) {
if (this.experiment != undefined && this.dataset != undefined) {
- let columnName = (<HTMLInputElement>event.currentTarget).value;
+
+ this.experiment.nullValuesReplacers[index].option = NullValueOptions.DeleteRows;
+ this.experiment.nullValuesReplacers[index].value = "";
+
+ this.nullValOption[index] = "Obriši redove (" + this.dataset.columnInfo[index].numNulls + ")";
+
+ this.columnTableChangeDetected();
+ }
+
+ /*let columnName = (<HTMLInputElement>event.currentTarget).value;
let arrayElement = this.experiment.nullValuesReplacers.filter(x => x.column == columnName)[0];
if (arrayElement == undefined) {
@@ -311,14 +410,21 @@ export class ColumnTableComponent implements AfterViewInit {
}
let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[index])[0]).numNulls;
- this.nullValOption[index] = (replacementType == NullValueOptions.DeleteColumns) ? "Obriši kolonu" : "Obriši redove (" + numOfRowsToDelete + ")";
+ this.nullValOption[index] = (replacementType == NullValueOptions.DeleteColumns) ? "Isključi kolonu" : "Obriši redove (" + numOfRowsToDelete + ")";
this.columnTableChangeDetected();
- }
+ }*/
}
MissValsReplaceClicked(event: Event, columnName: string, index: number) {
if (this.experiment != undefined) {
- let fillValue = (<HTMLInputElement>event.currentTarget).value;
+ this.experiment.nullValuesReplacers[index].option = NullValueOptions.Replace;
+ let value = (<HTMLInputElement>event.currentTarget).value;
+ this.experiment.nullValuesReplacers[index].value = value;
+ this.nullValOption[index] = "Popuni sa " + value;
+
+ this.columnTableChangeDetected();
+
+ /*let fillValue = (<HTMLInputElement>event.currentTarget).value;
let arrayElement = this.experiment.nullValuesReplacers.filter(x => x.column == columnName)[0];
if (arrayElement == undefined) {
@@ -334,7 +440,7 @@ export class ColumnTableComponent implements AfterViewInit {
}
this.nullValOption[index] = "Popuni sa: " + fillValue;
- this.columnTableChangeDetected();
+ this.columnTableChangeDetected();*/
}
}
getValue(columnName: string): string {
diff --git a/frontend/src/app/_elements/datatable/datatable.component.css b/frontend/src/app/_elements/datatable/datatable.component.css
index e69de29b..4e0a3737 100644
--- a/frontend/src/app/_elements/datatable/datatable.component.css
+++ b/frontend/src/app/_elements/datatable/datatable.component.css
@@ -0,0 +1,4 @@
+.proba {
+ max-width: 1620px;
+
+} \ No newline at end of file
diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css
index ada2dba0..682fc645 100644
--- a/frontend/src/app/_elements/folder/folder.component.css
+++ b/frontend/src/app/_elements/folder/folder.component.css
@@ -1,7 +1,6 @@
#folder {
- /*position: absolute;
- left: 50%;
- transform: translateX(-50%);*/
+ width: 100%;
+
}
#tabs {
@@ -107,8 +106,9 @@
.bottom-button {
font-size: large;
position: relative;
+ text-align: center;
background-color: var(--ns-primary);
- width: 10rem;
+ width: 12rem;
height: 2.3rem;
border-color: var(--ns-primary);
border-style: solid;
@@ -167,24 +167,26 @@
align-items: center;
flex-grow: 1;
height: 100%;
+ padding: 50px;
}
.folder-inside {
width: 100%;
- height: 40rem;
- overflow-y: auto;
+ min-height: 33rem;
+ height: 100%;
+ max-height: 100%;
}
.file-content {
width: 100%;
- height: 100%;
+ height: 95%;
position: relative;
}
.file-bottom-buttons {
position: absolute;
- bottom: 15px;
- right: 15px;
+ bottom: 5px;
+ right: 4%;
display: flex;
flex-direction: row-reverse;
}
diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html
index 8896e7e5..a557edde 100644
--- a/frontend/src/app/_elements/folder/folder.component.html
+++ b/frontend/src/app/_elements/folder/folder.component.html
@@ -3,7 +3,7 @@
<div id="new-file-tab" *ngIf="newFile" class="folder-tab p-1 rounded-top" [style]="'z-index:' + (selectedTab == TabType.NewFile ? 11 : 10) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.NewFile, 'hover-tab' : hoverTab == TabType.NewFile}">
<mat-icon class="text-offwhite">add</mat-icon>
<a class="stretched-link tab-link" (click)="selectTab(TabType.NewFile)" (mouseenter)="hoverOverTab(TabType.NewFile)" (mouseleave)="hoverOverTab(TabType.None)">
- {{newFile.name}}
+ {{newTabTitles[type]}}
</a>
</div>
<!--<div class="folder-tab p-1 rounded-top" *ngFor="let file of filteredFiles; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedFileIndex == i, 'hover-tab' : hoveringOverFileIndex == i}">
@@ -29,42 +29,48 @@
<button matSuffix class="btn-clear input-icon" (click)="clearSearchTerm()"><mat-icon>clear</mat-icon></button>
</mat-form-field>
</div>
- <div id="search-options">
- <!-- <div id="collapseFilters" class="collapse collapse-horizontal">
+ <div id="modelFilter" *ngIf="type == FolderType.Model && forExperiment">
+ Filter: {{forExperiment.type}}
+ </div>
+ <!--<div id="search-options">
+ <div id="collapseFilters" class="collapse collapse-horizontal">
<mat-icon class="text-offwhite ">timeline</mat-icon>
Regresioni
<mat-icon class="text-offwhite ">looks_two</mat-icon>
Binarni klasifikacioni
<mat-icon class="text-offwhite ">auto_awesome_motion</mat-icon>
Multiklasifikacioni
- </div> -->
+ </div>
<button class="btn-clear icon-toggle" data-bs-toggle="collapse" data-bs-target="#collapseFilters" aria-expanded="false" aria-controls="collapseFilters">
<mat-icon>filter_alt</mat-icon>
</button>
- <!-- <div id="collapseSort" class="collapse collapse-horizontal">
+ <div id="collapseSort" class="collapse collapse-horizontal">
[sort options here TODO]
- </div> -->
+ </div>
<button class="btn-clear icon-toggle" data-bs-toggle="collapse" data-bs-target="#collapseSort" aria-expanded="false" aria-controls="collapseSort">
<mat-icon>sort</mat-icon>
</button>
- <!-- <button class="btn-clear icon-toggle separator" [ngClass]="{'icon-toggle-on': listView}" (click)="toggleListView()">
+ <button class="btn-clear icon-toggle separator" [ngClass]="{'icon-toggle-on': listView}" (click)="toggleListView()">
<mat-icon>view_list</mat-icon>
- </button> -->
- </div>
+ </button>
+ </div>-->
</div>
<!--{{fileToDisplay ? fileToDisplay.name : 'No file selected.'}} {{selectedFileIndex}} {{hoveringOverFileIndex}}-->
<div class="folder-inside bg-blur">
<div class="file-content" [ngClass]="{'form-hidden' : listView}">
<div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile">
- <button *ngIf="this.selectedFile && selectedTab == TabType.File" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)">
- <mat-icon>delete</mat-icon>
+ <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"></app-form-model>
- <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment"></app-form-dataset>
+ <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}" [forExperiment]="forExperiment" [hideProblemType]="(forExperiment ? true : false)" [forProblemType]="(forExperiment ? forExperiment.type : ProblemType.Regression)" (editEvent)="onFileChange()"></app-form-model>
+ <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}" [forExperiment]="forExperiment" (editEvent)="onFileChange()"></app-form-dataset>
</div>
<div [ngClass]="{'form-hidden' : !listView}" class="list-view">
<div *ngFor="let file of filteredFiles; let i = index">
@@ -76,13 +82,21 @@
{{file.lastUpdated | date}}
</div>
<div class="mx-2 hover-show" *ngIf="selectedTab !== TabType.PublicDatasets && selectedTab !== TabType.PublicModels">
- <button class="btn-clear file-button" (click)="deleteFile(file, $event)">
- <mat-icon>delete</mat-icon>
+ <button *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>
+ <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>
+ </div>
+ <div class="mx-2 hover-show" *ngIf="selectedTab == TabType.PublicDatasets || selectedTab == TabType.PublicModels">
+ <button class="btn-clear file-button" (click)="addFile(file, $event)">
+ <mat-icon matTooltip="Uvezi" matTooltipPosition="right">note_add</mat-icon>
</button>
</div>
</div>
<div *ngIf="type == FolderType.Experiment" class="list-view">
- <div *ngFor="let predictor of predictorsForExp[file._id];" class="list-item">
+ <div *ngFor="let predictor of predictorsForExp[file._id];" class="list-item" (click)="goToExperimentPageWithPredictor(file, predictor)">
<div class="mx-3">
<div class="f-row">
@@ -94,7 +108,7 @@
{{predictor.lastUpdated | date}}
</div>
<div class="mx-2 hover-show">
- <button class="btn-clear file-button" (click)="deleteFile(predictor, $event)">
+ <button class="btn-clear file-button" (click)="deleteFile(predictor, $event, true)">
<mat-icon>delete</mat-icon>
</button>
</div>
@@ -102,9 +116,10 @@
</div>
</div>
- <div class="list-add" [ngSwitch]="type">
- <button mat-raised-button *ngSwitchCase="FolderType.Dataset" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javni ' : ' '}}izvor podataka</button>
- <button mat-raised-button *ngSwitchCase="FolderType.Model" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javnu ' : ' '}}konfiguraciju neuronske mreže</button>
+ <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>
<button mat-raised-button *ngSwitchCase="FolderType.Experiment" routerLink="/experiment">Dodaj eksperiment</button>
</div>
</div>
@@ -119,14 +134,30 @@
</div>
</div>
</button>
- <button mat-button (click)="ok()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="false">
- <div class="f-row">
- <div>Ok</div>
- <div class="icon-double pt-1">
- <mat-icon>check</mat-icon>
- <mat-icon>check</mat-icon>
+ <ng-container *ngSwitchCase="false">
+ <button mat-button (click)="ok()" class="bottom-button text-offwhite rounded-bottom" *ngIf="!selectedFileHasChanges && !archive">
+ <div class="f-row">
+ <div>Ok</div>
+ <div class="p-1 w-100" *ngIf="loadingAction">
+ <app-spinner></app-spinner>
+ </div>
+ <div class="icon-double pt-1" *ngIf="!loadingAction">
+ <mat-icon>check</mat-icon>
+ <mat-icon>check</mat-icon>
+ </div>
</div>
- </div>
- </button>
+ </button>
+ <button mat-button (click)="updateFile()" class="bottom-button text-offwhite rounded-bottom" *ngIf="selectedFileHasChanges" [disabled]="loadingAction">
+ <div class="f-row">
+ <div *ngIf="!loadingAction">Sačuvaj izmene</div>
+ <div class="pt-1" *ngIf="!loadingAction">
+ <mat-icon>edit</mat-icon>
+ </div>
+ <div class="pt-1 w-100" *ngIf="loadingAction">
+ <app-spinner></app-spinner>
+ </div>
+ </div>
+ </button>
+ </ng-container>
</div>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts
index 665659a8..93e8ba98 100644
--- a/frontend/src/app/_elements/folder/folder.component.ts
+++ b/frontend/src/app/_elements/folder/folder.component.ts
@@ -1,7 +1,7 @@
import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import Dataset from 'src/app/_data/Dataset';
import { FolderFile, FolderType } from 'src/app/_data/FolderFile';
-import Model from 'src/app/_data/Model';
+import Model, { ProblemType } from 'src/app/_data/Model';
import { DatasetsService } from 'src/app/_services/datasets.service';
import Shared from 'src/app/Shared';
import { ModelsService } from 'src/app/_services/models.service';
@@ -11,8 +11,10 @@ import { ExperimentsService } from 'src/app/_services/experiments.service';
import { PredictorsService } from 'src/app/_services/predictors.service';
import { SignalRService } from 'src/app/_services/signal-r.service';
import { FormModelComponent } from '../form-model/form-model.component';
-import { Router } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import Predictor from 'src/app/_data/Predictor';
+import FileSaver from 'file-saver';
+import isEqual from 'lodash.isequal';
@Component({
selector: 'app-folder',
@@ -42,11 +44,12 @@ export class FolderComponent implements AfterViewInit {
fileToDisplay?: FolderFile;
@Output() selectedFileChanged: EventEmitter<FolderFile> = new EventEmitter();
+ @Output() fileFromRoute: EventEmitter<FolderFile> = new EventEmitter();
@Output() okPressed: EventEmitter<string> = new EventEmitter();
searchTerm: string = '';
- constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router) {
+ constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute) {
this.tabsToShow.forEach(tab => this.folders[tab] = []);
}
@@ -65,8 +68,10 @@ export class FolderComponent implements AfterViewInit {
}
displayFile() {
- if (this.type == FolderType.Dataset)
+ 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;
}
@@ -93,15 +98,18 @@ export class FolderComponent implements AfterViewInit {
this.newFileSelected = true;
this.listView = false;
this.displayFile();
- if (this.type == FolderType.Dataset)
+ if (this.type == FolderType.Dataset) {
this.formDataset.clear();
+ }
}
selectFile(file?: FolderFile) {
+ this.formDataset.resetPagging();
this.selectedFile = file;
+ Object.assign(this.lastFileData, this.selectedFile);
this.fileToDisplay = file;
if (this.type == FolderType.Experiment && file) {
- this.router.navigate(['/experiment'/*, file._id*/])
+ this.router.navigate(['/experiment/' + file._id]);
}
this.newFileSelected = false;
this.listView = false;
@@ -113,6 +121,10 @@ export class FolderComponent implements AfterViewInit {
this.formDataset.loadExisting();
}
+ goToExperimentPageWithPredictor(file: FolderFile, predictor: Predictor) {
+ this.router.navigate(['/experiment/p/' + predictor._id]);
+ }
+
createNewFile() {
if (this.type == FolderType.Dataset) {
this.newFile = new Dataset();
@@ -128,10 +140,7 @@ export class FolderComponent implements AfterViewInit {
_initialized: boolean = false;
refreshFiles(selectedDatasetId: string | null = null, selectedModelId: string | null = null) {
- this.files = []
- this.filteredFiles.length = 0;
- this.folders[TabType.NewFile] = [];
- this.folders[TabType.File] = [];
+
this.tabsToShow.forEach(tab => {
this.folders[tab] = [];
});
@@ -173,6 +182,9 @@ export class FolderComponent implements AfterViewInit {
this.folders[TabType.MyModels] = models;
if (selectedModelId) {
this.selectFile(models.filter(x => x._id == selectedModelId)[0]);
+ setTimeout(() => {
+ this.okPressed.emit();
+ });
}
this.searchTermsChanged();
});
@@ -180,12 +192,18 @@ export class FolderComponent implements AfterViewInit {
this.folders[TabType.PublicModels] = models;
this.searchTermsChanged();
});*/
- this.folders[TabType.PublicModels] = [];
+
+ this.modelsService.getPublicModels().subscribe((models) => {
+ this.folders[TabType.PublicModels] = models;
+ this.searchTermsChanged();
+ });
+ //this.folders[TabType.PublicModels] = [];
}
refreshDatasets(selectedDatasetId: string | null) {
this.datasetsService.getMyDatasets().subscribe((datasets) => {
this.folders[TabType.MyDatasets] = datasets;
+ console.log(this.filteredFiles);
if (selectedDatasetId) {
this.selectFile(datasets.filter(x => x._id == selectedDatasetId)[0]);
}
@@ -208,7 +226,7 @@ export class FolderComponent implements AfterViewInit {
this.predictorsForExp[exp._id].forEach(pred => {
const model = this.folders[TabType.MyModels].find(model => model._id == pred.modelId);
pred.name = model?.name!;
- pred.lastUpdated = model?.lastUpdated!;
+ //pred.lastUpdated = model?.lastUpdated!;
})
/* ------------------------------------------------ */
this.searchTermsChanged();
@@ -218,11 +236,14 @@ export class FolderComponent implements AfterViewInit {
}
saveNewFile() {
+ this.loadingAction = true;
switch (this.type) {
case FolderType.Dataset:
this.formDataset!.uploadDataset((dataset: Dataset) => {
this.newFile = undefined;
- Shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira.");
+ this.loadingAction = false;
+ 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();
},
() => {
@@ -230,9 +251,11 @@ export class FolderComponent implements AfterViewInit {
});
break;
case FolderType.Model:
+ this.formModel.newModel.type = this.formModel.forProblemType;
this.modelsService.addModel(this.formModel.newModel).subscribe(model => {
this.newFile = undefined;
- Shared.openDialog("Obaveštenje", "Uspešno ste dodali novu konfiguraciju neuronske mreže u kolekciju.");
+ this.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
}, (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.");
@@ -253,7 +276,12 @@ export class FolderComponent implements AfterViewInit {
searchTermsChanged() {
this.filteredFiles.length = 0;
if (!this.files) return;
- this.filteredFiles.push(...this.files.filter((file) => file.name.toLowerCase().includes(this.searchTerm.toLowerCase())));
+ this.filteredFiles.push(...this.files.filter((file) => {
+ return (file.name.toLowerCase().includes(this.searchTerm.toLowerCase())
+ && (!this.forExperiment
+ || this.type != FolderType.Model
+ || (this.type == FolderType.Model && (<Model>file).type == this.forExperiment.type)))
+ }));
/*if (this.selectedFile) {
if (!this.filteredFiles.includes(this.selectedFile)) {
if (this.hoverTab === TabType.None && this.getFolderType(this.selectedTab) === this.type) {
@@ -268,23 +296,136 @@ export class FolderComponent implements AfterViewInit {
listView: boolean = true;
- deleteFile(file: FolderFile, event: Event) {
+ loadingAction = false;
+ selectedFileHasChanges = false;
+ lastFileData = {};
+
+ onFileChange() {
+ console.log(this.selectedFile, this.lastFileData)
+ setTimeout(() => {
+ this.selectedFileHasChanges = !((this.selectedTab == TabType.NewFile) || isEqual(this.selectedFile, this.lastFileData));
+ });
+ }
+
+ updateFile() {
+ const file = this.selectedFile;
+ this.loadingAction = true;
+ switch (this.type) {
+ case FolderType.Dataset:
+ this.datasetsService.editDataset(<Dataset>file).subscribe((response) => {
+ this.fileUpdatedSuccess();
+ });
+ break;
+ case FolderType.Model:
+ this.modelsService.editModel(<Model>file).subscribe((response) => {
+ this.fileUpdatedSuccess();
+ });
+ break;
+ }
+ }
+
+ fileUpdatedSuccess() {
+ this.loadingAction = false;
+ this.selectedFileHasChanges = false;
+ Object.assign(this.lastFileData, this.selectedFile);
+ this.refreshFiles();
+ }
+
+ deleteFile(file: FolderFile, event: Event, deletePredictor: boolean = false) {
event.stopPropagation();
- //console.log('delete');
+
switch (this.type) {
case FolderType.Dataset:
- this.datasetsService.deleteDataset(<Dataset>file).subscribe((response) => {
+ const dataset = <Dataset>file;
+ Shared.openYesNoDialog("Obriši izvor podataka", "Eksperimenti i trenirani modeli nad ovim izvorom podataka će takođe biti obrisani, da li ste sigurni da želite da obrišete izvor: " + dataset.name + "?", () => {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ this.loadingAction = true;
+ this.datasetsService.deleteDataset(dataset).subscribe((response) => {
+ this.loadingAction = false;
+ });
+ })
+ break;
+ case FolderType.Model:
+ const model = <Model>file;
+ Shared.openYesNoDialog("Obriši konfiguraciju neuronske mreže", "Trenirani modeli za ovu konfiguraciju će takođe biti obrisani, da li ste sigurni da želite da obrišete konfiguraciju: " + model.name + "?", () => {
this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ this.loadingAction = true;
+ this.modelsService.deleteModel(<Model>file).subscribe((response) => {
+ this.loadingAction = false;
+ });
+ })
+
+ break;
+ case FolderType.Experiment:
+ if (deletePredictor) {
+ const predictor = <Predictor>file;
+ Shared.openYesNoDialog("Obriši trenirani model", "Da li ste sigurni da želite da obrišete trenirani model: " + predictor.name + "?", () => {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ this.loadingAction = true;
+ this.predictorsService.deletePredictor(predictor).subscribe((response) => {
+ this.loadingAction = false;
+ });
+ });
+ } else {
+ const experiment = <Experiment>file;
+ Shared.openYesNoDialog("Obriši eksperiment", "Trenirani modeli za ovaj eksperiment će takođe biti obrisani, da li ste sigurni da želite da obrišete eksperiment: " + experiment.name + "?", () => {
+ this.filteredFiles.splice(this.filteredFiles.indexOf(file), 1);
+ this.files.splice(this.files.indexOf(file), 1);
+ this.loadingAction = true;
+ this.experimentsService.deleteExperiment(experiment).subscribe((response) => {
+ this.loadingAction = false;
+ });
+ });
+ }
+ break;
+ }
+ }
+ downloadFile(file: FolderFile, event: Event) {
+ event.stopPropagation();
+ if (this.type == FolderType.Dataset) {
+ const fileId = (<Dataset>file).fileId;
+ const name = (<Dataset>file).name;
+ const ext = (<Dataset>file).extension;
+ if (fileId != undefined)
+ this.datasetsService.downloadFile(fileId).subscribe((response) => {
+ FileSaver.saveAs(response, name + ext);
+
+ });
+
+ }
+ }
+
+ addFile(file: FolderFile, event: Event) {
+ event.stopPropagation();
+ switch (this.type) {
+ case FolderType.Dataset:
+ (<Dataset>file)._id = "";
+ (<Dataset>file).isPreProcess = true;
+ (<Dataset>file).isPublic = false;
+ this.datasetsService.stealDataset(<Dataset>file).subscribe((response) => {
+ Shared.openDialog("Obaveštenje", "Uspešno ste dodali javni izvor podataka u vašu kolekciju.");
this.refreshFiles(null);
+ }, (error: any) => {
+ if (error.error == "Dataset with this name already exists") {
+ Shared.openDialog("Obaveštenje", "Izvor podataka sa ovim imenom postoji u vašoj kolekciji.");
+ }
});
break;
case FolderType.Model:
- this.modelsService.deleteModel(<Model>file).subscribe((response) => {
+ this.modelsService.stealModel(<Model>file).subscribe((response) => {
+ Shared.openDialog("Obaveštenje", "Uspešno ste dodali javnu konfiguraciju neuronske mreže u vašu kolekciju.");
this.refreshFiles(null);
+ }, (error: any) => {
+ if (error.error == "Model already exisits or validation size is not between 0-1") {
+ Shared.openDialog("Obaveštenje", "Model sa ovim imenom postoji u vašoj kolekciji.");
+ }
});
break;
case FolderType.Experiment:
- // this.experimentsService.deleteExperiment(<Model>file).subscribe((response) => {
+ // this.experimentsService.addExperiment(<Model>file).subscribe((response) => {
// console.log(response);
// });
//todo delete za predictor
@@ -305,6 +446,7 @@ export class FolderComponent implements AfterViewInit {
};
FolderType = FolderType;
+ ProblemType = ProblemType;
Privacy = Privacy;
TabType = TabType;
@@ -325,6 +467,7 @@ export class FolderComponent implements AfterViewInit {
setTimeout(() => {
if (tab == TabType.NewFile) {
this.selectNewFile();
+ this.selectedFile=undefined!;
}
this.listView = this.getListView(tab);
@@ -395,9 +538,16 @@ export class FolderComponent implements AfterViewInit {
updateExperiment() {
if (this.formModel) {
- this.formModel.updateGraph();
+ setTimeout(() => {
+ this.formModel.updateGraph();
+ });
}
}
+
+ newTabTitles: { [tab: number]: string } = {
+ [FolderType.Dataset]: 'Novi izvor podataka',
+ [FolderType.Model]: 'Nova konfiguracija neuronske mreže',
+ };
}
export enum Privacy {
diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.css b/frontend/src/app/_elements/form-dataset/form-dataset.component.css
index 079711d0..43accd6d 100644
--- a/frontend/src/app/_elements/form-dataset/form-dataset.component.css
+++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.css
@@ -7,7 +7,7 @@
.topBar {
display: table;
width: 100%;
- height: 100px;
+ height: 80px;
margin-left: 1.5%;
margin-top: 1.5% ;
}
@@ -63,4 +63,8 @@
.file-container input{
border-radius: 5px;
left: 0%;
+}
+
+.naslov{
+ font-size: 30px;
} \ No newline at end of file
diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html
index b96276bd..6194a1a8 100644
--- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html
+++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html
@@ -1,52 +1,51 @@
<div class="folderBox" *ngIf="dataset">
+ <div class="topBar">
+ <div class="kolona mb-3">
+ <div class="fileButton">
+ <button type="button" mat-raised-button (click)="fileInput.click()"><span *ngIf="!firstInput">Dodaj izvor podataka</span><span *ngIf="firstInput">{{filename}}</span></button>
+ </div>
+ </div>
-
- <div class="topBar">
- <div class="kolona mb-3">
- <div class="fileButton">
- <button type="button" mat-raised-button (click)="fileInput.click()">Dodaj izvor podataka</button>
-
- </div>
- </div>
-
- <div class="kolona">
- <div role="group">
- <mat-form-field class="example-full-width" appearance="fill">
- <mat-label>Naziv</mat-label>
- <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name">
+ <div class="kolona">
+ <div role="group">
+ <mat-form-field class="example-full-width" appearance="fill">
+ <mat-label>Naziv</mat-label>
+ <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()">
- <mat-error *ngIf="nameFormControl.hasError('required')">
- Naziv je <strong>obavezan</strong>
- </mat-error>
- </mat-form-field>
- </div>
- </div>
- <div class="kolona">
- <mat-form-field appearance="fill">
- <mat-label>Delimiter</mat-label>
- <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update()" value=",">
- <mat-option *ngFor="let option of delimiterOptions" [value]="option">
- {{ option }}
- </mat-option>
- </mat-select>
- </mat-form-field>
- </div>
+ <mat-error *ngIf="nameFormControl.hasError('required')">
+ Naziv je <strong>obavezan</strong>
+ </mat-error>
+ </mat-form-field>
+ </div>
+ </div>
+ <div class="kolona">
+ <mat-form-field appearance="fill">
+ <mat-label>Delimiter</mat-label>
+ <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value=",">
+ <mat-option *ngFor="let option of delimiterOptions" [value]="option">
+ {{ option }}
+ </mat-option>
+ </mat-select>
+ </mat-form-field>
</div>
-
- <div class="row" *ngIf="firstInput">
- <label class=" mt-5">{{filename}}</label>
-
</div>
+
<div class="row" style="margin-right: 0;">
<div class="file-container" [ngClass]="{'dottedClass': !tableData.hasInput}">
<i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i>
- <input class="file" id="file-upload" (change)="changeListener($event)" #fileInput type="file" accept=".csv">
- <div class="mt-5 datatable">
+ <input class="file" id="file-upload" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv">
+
+ <div class="datatable">
+ <div [ngClass]="{'hidden': (!existingFlag)}" class="text-center">
+ <button mat-button (click)="goBack()"><mat-icon>keyboard_arrow_left</mat-icon></button>
+ <div style="display: inline;">{{(this.begin/10)+1}}...{{getPage()}}</div>
+ <button mat-button (click)="goForward()"><mat-icon>keyboard_arrow_right</mat-icon></button>
+ </div>
<app-datatable [tableData]="tableData"></app-datatable>
</div>
diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
index 94ef9905..35d68526 100644
--- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
+++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts
@@ -24,8 +24,12 @@ export class FormDatasetComponent {
files: File[] = [];
rowsNumber: number = 0;
colsNumber: number = 0;
+ begin: number = 0;
+ step: number = 10;
+ existingFlag: boolean = false;
@Input() dataset: Dataset; //dodaj ! potencijalno
+ @Output() editEvent = new EventEmitter();
tableData: TableData = new TableData();
@@ -40,10 +44,31 @@ export class FormDatasetComponent {
}
//@ViewChild('fileImportInput', { static: false }) fileImportInput: any; cemu je ovo sluzilo?
+ resetPagging() {
+ this.begin = 0;
+ }
+ goBack() {
+ if (this.begin - 10 < 0)
+ this.begin = 0;
+ else {
+ this.begin -= 10;
+ this.loadExisting();
+ }
- clear(){
+ }
+ goForward() {
+ this.begin += 10;
+ if (this.dataset.rowCount < this.begin)
+ this.begin -= 10;
+ else
+ this.loadExisting();
+ }
+ clear() {
this.tableData.hasInput = false;
}
+ getPage() {
+ return Math.ceil(this.dataset.rowCount / 10)
+ }
changeListener($event: any): void {
this.files = $event.srcElement.files;
@@ -56,6 +81,7 @@ export class FormDatasetComponent {
this.filename = this.files[0].name;
this.tableData.loaded = false;
+ this.existingFlag = false;
this.update();
}
@@ -64,7 +90,6 @@ export class FormDatasetComponent {
update() {
this.firstInput = true
-
if (this.files.length < 1)
return;
@@ -90,23 +115,30 @@ export class FormDatasetComponent {
this.dataset.name = this.filename.slice(0, this.filename.length - 4);
}
- loadExisting(){
+ loadExisting() {
+ this.existingFlag = true;
this.firstInput = false;
this.tableData.hasInput = true;
this.tableData.loaded = false;
-
- this.datasetsService.getDatasetFile(this.dataset.fileId).subscribe((file: string | undefined) => {
- if (file) {
- this.tableData.loaded = true;
- this.tableData.numRows = this.dataset.rowCount;
- this.tableData.numCols = this.dataset.columnInfo.length;
- this.tableData.data = this.csv.csvToArray(file, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter);
-
- }
+ this.datasetsService.getDatasetHeader(this.dataset.fileId).subscribe((header: string | undefined) => {
+
+ this.datasetsService.getDatasetFilePaging(this.dataset.fileId, this.begin, this.step).subscribe((file: string | undefined) => {
+ if (file) {
+ this.tableData.loaded = true;
+ this.tableData.numRows = this.dataset.rowCount;
+ this.tableData.numCols = this.dataset.columnInfo.length;
+ this.tableData.data = this.csv.csvToArray(header + '\n' + file, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter);
+
+ }
+ else {
+ this.begin -= 10;
+ this.loadExisting();
+ }
+ });
});
-
+
}
/*exportAsXLSX():void {
diff --git a/frontend/src/app/_elements/form-model/form-model.component.css b/frontend/src/app/_elements/form-model/form-model.component.css
index 95ace1ef..34c1a5c5 100644
--- a/frontend/src/app/_elements/form-model/form-model.component.css
+++ b/frontend/src/app/_elements/form-model/form-model.component.css
@@ -86,12 +86,12 @@ hr {
max-height: 20 rem;
}
-mat-slider {
- width: 50%;
-}
-
.slider {
background-color: transparent;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
}
.center-center {
@@ -102,3 +102,29 @@ mat-slider {
font-size: 20px !important;
font-weight: 600;
}
+
+.slider-pad {
+ min-width: 10%;
+ background-color: gray;
+ height: 2px;
+}
+
+.slider-pad:first-child {
+ margin-right: -8px;
+}
+
+.slider-pad:last-child {
+ margin-left: -8px;
+}
+
+.slider-extended {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ width: 95%;
+}
+
+.slide {
+ width: 80%;
+} \ No newline at end of file
diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html
index 09e44a99..b986e154 100644
--- a/frontend/src/app/_elements/form-model/form-model.component.html
+++ b/frontend/src/app/_elements/form-model/form-model.component.html
@@ -5,18 +5,13 @@
<div class="ns-col">
<mat-form-field class="example-full-width" appearance="fill" class="mat-fix">
<mat-label>Naziv</mat-label>
- <input type="text" matInput [(ngModel)]="newModel.name">
+ <input type="text" matInput [(ngModel)]="newModel.name" (valueChange)="editEvent.emit()">
</mat-form-field>
</div>
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Tip problema</mat-label>
- <mat-select [(ngModel)]="newModel.type" (selectionChange)="filterLossFunction()" *ngIf="this.hideProblemType" disabled="true">
- <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option">
- {{ optionName }}
- </mat-option>
- </mat-select>
- <mat-select [(ngModel)]="newModel.type" (selectionChange)="filterLossFunction()" *ngIf="!this.hideProblemType" disabled="false">
+ <mat-select [(ngModel)]="forProblemType" [disabled]="this.hideProblemType" (valueChange)="editEvent.emit(); newModel.type = forProblemType;">
<mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option">
{{ optionName }}
</mat-option>
@@ -29,7 +24,7 @@
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Optimizacija</mat-label>
- <mat-select [(ngModel)]="newModel.optimizer">
+ <mat-select [(ngModel)]="newModel.optimizer" (valueChange)="editEvent.emit()">
<mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option">
{{ optionName }}
</mat-option>
@@ -40,8 +35,8 @@
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Funkcija troška</mat-label>
- <mat-select [(ngModel)]="newModel.lossFunction">
- <mat-option *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" [value]="option">
+ <mat-select [(ngModel)]="lossFunction" (valueChange)="newModel.lossFunction = lossFunction!; editEvent.emit();">
+ <mat-option *ngFor="let option of Object.keys(lossFunctions[forProblemType]); let optionName of Object.values(lossFunctions[forProblemType])" [value]="option">
{{ optionName }}
</mat-option>
</mat-select>
@@ -53,7 +48,7 @@
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Funkcija aktivacije izlaznog sloja</mat-label>
- <mat-select name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction">
+ <mat-select name="outputLayerActivationFunction" [(ngModel)]="outputLayerActivationFunction" (valueChange)="newModel.outputLayerActivationFunction = outputLayerActivationFunction!; editEvent.emit();">
<mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option">
{{ optionName }}
</mat-option>
@@ -63,7 +58,7 @@
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Stopa učenja</mat-label>
- <mat-select [(ngModel)]="newModel.learningRate">
+ <mat-select [(ngModel)]="newModel.learningRate" (valueChange)="editEvent.emit()">
<mat-option *ngFor="let option of Object.keys(LearningRate); let optionName of Object.values(LearningRate)" [value]="option">
{{ optionName }}
</mat-option>
@@ -76,14 +71,14 @@
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Broj epoha</mat-label>
- <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000">
+ <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000" (valueChange)="editEvent.emit()">
</mat-form-field>
</div>
<div class="ns-col">
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Broj uzoraka po iteraciji</mat-label>
- <mat-select matNativeControl [(value)]="newModel.batchSize">
+ <mat-select matNativeControl [(value)]="newModel.batchSize" (valueChange)="editEvent.emit()">
<mat-option *ngFor="let option of Object.keys(BatchSize); let optionName of Object.values(BatchSize)" [value]="option">{{option}}</mat-option>
</mat-select>
</mat-form-field>
@@ -96,26 +91,38 @@
<div class="m-2">
<div class="row">
- <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px;">
- <div class="row slider rounded mb-3" style="margin-left: 10px;">
-
+ <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px; width: fit-content;">
+ <!-- <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">Trening
- <mat-slider min="10" max="90" step="10" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)"></mat-slider>
- Test</div>
-
- </div>
- <div class="row slider rounded text-offwhite justify-content-center align-items-center" style="margin-left: 10px;">
- <mat-checkbox class="pt-4 mb-3" color="accent">Nasumični redosled podataka</mat-checkbox>
+ <div class="text-center pt-0 mt-0 p-0">Trening ////slider////// Test</div>
+ -->
+ <div class="slider mt-2">
+ <div>
+ Trening {{testSetDistribution}}% : Test i validacija {{100-testSetDistribution}}%
+ </div>
+ <div class="slider-extended">
+ <div class="slider-pad"></div>
+ <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)" (valueChange)="editEvent.emit()"></mat-slider>
+ <div class="slider-pad"></div>
+ </div>
+ <div>
+ Test {{validationSize}}% : Validacija {{100-validationSize}}%
+ </div>
+ <div class="slider-extended">
+ <div class="slider-pad"></div>
+ <mat-slider class="slide" min="10" max="90" step="5" [(ngModel)]="validationSize" (input)="updateValidation($event)" (valueChange)="editEvent.emit()"></mat-slider>
+ <div class="slider-pad"></div>
+ </div>
</div>
-
+ <div class="d-flex justify-content-center mt-3">
+ <mat-checkbox color="accent" (valueChange)="editEvent.emit()">Nasumični redosled podataka</mat-checkbox>
+ </div>
</div>
<div class="col-sm-9">
<!-- {{forExperiment._columnsSelected}} -->
- <app-graph [model]="newModel" *ngIf="forExperiment._columnsSelected" [inputColumns]="getInputColumns()"></app-graph>
- <app-graph [model]="newModel" *ngIf="!forExperiment._columnsSelected" [inputColumns]="['Nisu odabrane ulazne kolone']"></app-graph>
+ <app-graph [model]="newModel" [inputColumns]="getInputColumns()"></app-graph>
</div>
</div>
</div>
@@ -213,7 +220,7 @@
<mat-form-field appearance="fill" class="mat-fix">
<mat-label>Regularizacija</mat-label>
- <mat-select [(ngModel)]="newModel.layers[i].regularisation">
+ <mat-select [(ngModel)]="newModel.layers[i].regularisation" (valueChange)="editEvent.emit()">
<mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option">
{{ optionName }}
</mat-option>
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 e01c2339..646af08c 100644
--- a/frontend/src/app/_elements/form-model/form-model.component.ts
+++ b/frontend/src/app/_elements/form-model/form-model.component.ts
@@ -15,15 +15,22 @@ export class FormModelComponent implements AfterViewInit {
@ViewChild(GraphComponent) graph!: GraphComponent;
@Input() forExperiment!: Experiment;
@Output() selectedModelChangeEvent = new EventEmitter<Model>();
- @Input() hideProblemType:boolean;
- @Input() forProblemType:ProblemType;
+ @Input() hideProblemType!: boolean;
+ @Input() forProblemType!: ProblemType;
testSetDistribution: number = 70;
- constructor() {
- this.hideProblemType=false;
- this.forProblemType=ProblemType.BinaryClassification;
+ validationSize: number = 15;
+ constructor() {
}
- ngAfterViewInit(): void { }
+ @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);
nameFormControl = new FormControl('', [Validators.required, Validators.email]);
@@ -58,10 +65,21 @@ export class FormModelComponent implements AfterViewInit {
term: string = "";
selectedMetrics = [];
- lossFunction: any = LossFunction;
+ lossFunctions: { [index: string]: LossFunction[] } = {
+ [ProblemType.Regression]: LossFunctionRegression,
+ [ProblemType.BinaryClassification]: LossFunctionBinaryClassification,
+ [ProblemType.MultiClassification]: LossFunctionMultiClassification
+ };
+
+ outputLayerActivationFunctions: { [index: string]: ActivationFunction[] } = {
+ [ProblemType.Regression]: [ActivationFunction.Linear],
+ [ProblemType.BinaryClassification]: [ActivationFunction.Sigmoid],
+ [ProblemType.MultiClassification]: [ActivationFunction.Softmax]
+ };
loadModel(model: Model) {
this.newModel = model;
+ this.forProblemType = model.type;
}
updateGraph() {
@@ -74,17 +92,20 @@ export class FormModelComponent implements AfterViewInit {
this.newModel.layers.splice(this.newModel.layers.length - 1, 1);
this.newModel.hiddenLayers -= 1;
this.updateGraph();
+ this.editEvent.emit();
}
}
+
addLayer() {
- if (this.newModel.hiddenLayers < 128) {
+ if (this.newModel.hiddenLayers < 150) {
this.newModel.layers.push(new Layer(this.newModel.layers.length, this.selectedActivation, this.selectedNumberOfNeurons, this.selectedRegularisation, this.selectedRegularisationRate));
this.newModel.hiddenLayers += 1;
this.updateGraph();
+ this.editEvent.emit();
}
-
}
+
numSequence(n: number): Array<number> {
return Array(n);
}
@@ -93,59 +114,64 @@ export class FormModelComponent implements AfterViewInit {
if (this.newModel.layers[index].neurons > 1) {
this.newModel.layers[index].neurons -= 1;
this.updateGraph();
+ this.editEvent.emit();
}
}
+
addNeuron(index: number) {
if (this.newModel.layers[index].neurons < 18) {
this.newModel.layers[index].neurons += 1;
this.updateGraph();
+ this.editEvent.emit();
}
}
+
selectedActivation: ActivationFunction = ActivationFunction.Sigmoid;
selectedRegularisationRate: RegularisationRate = RegularisationRate.RR1;
selectedRegularisation: Regularisation = Regularisation.L1;
selectedNumberOfNeurons: number = 3;
+ lossFunction: LossFunction = LossFunction.MeanAbsoluteError;
+ outputLayerActivationFunction: ActivationFunction = ActivationFunction.Linear;
+
changeAllActivation() {
for (let i = 0; i < this.newModel.layers.length; i++) {
this.newModel.layers[i].activationFunction = this.selectedActivation;
}
+ this.editEvent.emit();
}
changeAllRegularisation() {
for (let i = 0; i < this.newModel.layers.length; i++) {
this.newModel.layers[i].regularisation = this.selectedRegularisation;
}
+ this.editEvent.emit();
}
changeAllRegularisationRate() {
for (let i = 0; i < this.newModel.layers.length; i++) {
this.newModel.layers[i].regularisationRate = this.selectedRegularisationRate;
}
+ this.editEvent.emit();
}
changeAllNumberOfNeurons() {
for (let i = 0; i < this.newModel.layers.length; i++) {
this.newModel.layers[i].neurons = this.selectedNumberOfNeurons;
- this.updateGraph();
}
+ this.updateGraph();
+ this.editEvent.emit();
}
updateTestSet(event: MatSliderChange) {
this.testSetDistribution = event.value!;
}
- filterLossFunction() {
- if(this.newModel.type==ProblemType.Regression){
- this.lossFunction = LossFunctionRegression;
- this.newModel.lossFunction=LossFunction.MeanSquaredError;
- }
- else if(this.newModel.type==ProblemType.BinaryClassification){
- this.lossFunction= LossFunctionBinaryClassification;
- this.newModel.lossFunction=LossFunction.BinaryCrossEntropy;
- }
- else if(this.newModel.type==ProblemType.MultiClassification){
- this.lossFunction = LossFunctionMultiClassification;
- this.newModel.lossFunction=LossFunction.SparseCategoricalCrossEntropy;
- }
-
-}
-getInputColumns() {
- return this.forExperiment.inputColumns.filter(x => x != this.forExperiment.outputColumn);
-}
+
+ getInputColumns() {
+ if (this.forExperiment)
+ return this.forExperiment.inputColumns.filter(x => x != this.forExperiment.outputColumn);
+ else
+ return ['Nisu odabrane ulazne kolone.']
+ }
+
+ updateValidation(event: MatSliderChange) {
+ this.validationSize = event.value!;
+ this.editEvent.emit();
+ }
}
diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html
index 35753d40..411e8b53 100644
--- a/frontend/src/app/_elements/graph/graph.component.html
+++ b/frontend/src/app/_elements/graph/graph.component.html
@@ -1,4 +1,4 @@
-<div #graphWrapper class="w-100 position-relative" style="height: 14rem;">
+<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
diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.ts b/frontend/src/app/_elements/metric-view/metric-view.component.ts
index fbca2edf..65b1ef9b 100644
--- a/frontend/src/app/_elements/metric-view/metric-view.component.ts
+++ b/frontend/src/app/_elements/metric-view/metric-view.component.ts
@@ -16,7 +16,7 @@ export class MetricViewComponent implements OnInit {
history: any[] = [];
- update(history: any[]) {
+ update(history: any[],totalEpochs:number) {
const myAcc: number[] = [];
const myMae: number[] = [];
const myMse: number[] = [];
@@ -29,7 +29,15 @@ export class MetricViewComponent implements OnInit {
const myEpochs: number[] = [];
this.history = history;
this.history.forEach((metrics, epoch) => {
- myEpochs.push(epoch + 1);
+ 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];
//console.log(key, ':::', value, epoch);
diff --git a/frontend/src/app/_elements/navbar/navbar.component.html b/frontend/src/app/_elements/navbar/navbar.component.html
index 105151aa..8cc742eb 100644
--- a/frontend/src/app/_elements/navbar/navbar.component.html
+++ b/frontend/src/app/_elements/navbar/navbar.component.html
@@ -9,7 +9,7 @@
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><a routerLink="experiment" class="nav-link px-2" [class]="(currentUrl === '/experiment') ? 'text-primary' : 'text-offwhite'">Napravi eksperiment</a>
</li>
- <li><a routerLink="archive" class="nav-link px-2" [class]="(currentUrl === '/archive') ? 'text-primary' : 'text-offwhite'">Arhiva</a>
+ <li><a routerLink="archive" class="nav-link px-2" [class]="(currentUrl === '/archive') ? 'text-primary' : 'text-offwhite'">Kolekcije</a>
</li>
</ul>
diff --git a/frontend/src/app/_elements/spinner/spinner.component.css b/frontend/src/app/_elements/spinner/spinner.component.css
new file mode 100644
index 00000000..78adc872
--- /dev/null
+++ b/frontend/src/app/_elements/spinner/spinner.component.css
@@ -0,0 +1,78 @@
+.wrap {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.loader {
+ color: #ffffff;
+ font-size: 20px;
+ margin: auto;
+ width: 1em;
+ height: 1em;
+ border-radius: 50%;
+ text-indent: -9999em;
+ -webkit-animation: load4 1.3s infinite linear;
+ animation: load4 1.3s infinite linear;
+ -webkit-transform: scale(0.2);
+ -ms-transform: scale(0.2);
+ transform: scale(0.2);
+}
+
+@-webkit-keyframes load4 {
+ 0%,
+ 100% {
+ box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
+ }
+ 12.5% {
+ box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
+ }
+ 25% {
+ box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
+ }
+ 37.5% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
+ }
+ 50% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
+ }
+ 62.5% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
+ }
+ 75% {
+ box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
+ }
+ 87.5% {
+ box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
+ }
+}
+
+@keyframes load4 {
+ 0%,
+ 100% {
+ box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
+ }
+ 12.5% {
+ box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
+ }
+ 25% {
+ box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
+ }
+ 37.5% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
+ }
+ 50% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
+ }
+ 62.5% {
+ box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
+ }
+ 75% {
+ box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
+ }
+ 87.5% {
+ box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
+ }
+} \ No newline at end of file
diff --git a/frontend/src/app/_elements/spinner/spinner.component.html b/frontend/src/app/_elements/spinner/spinner.component.html
new file mode 100644
index 00000000..c655abf0
--- /dev/null
+++ b/frontend/src/app/_elements/spinner/spinner.component.html
@@ -0,0 +1,3 @@
+<div class="wrap">
+ <div class="loader">Loading...</div>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_elements/spinner/spinner.component.spec.ts b/frontend/src/app/_elements/spinner/spinner.component.spec.ts
new file mode 100644
index 00000000..061f78d5
--- /dev/null
+++ b/frontend/src/app/_elements/spinner/spinner.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SpinnerComponent } from './spinner.component';
+
+describe('SpinnerComponent', () => {
+ let component: SpinnerComponent;
+ let fixture: ComponentFixture<SpinnerComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ SpinnerComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SpinnerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/_elements/spinner/spinner.component.ts b/frontend/src/app/_elements/spinner/spinner.component.ts
new file mode 100644
index 00000000..f0080edd
--- /dev/null
+++ b/frontend/src/app/_elements/spinner/spinner.component.ts
@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-spinner',
+ templateUrl: './spinner.component.html',
+ styleUrls: ['./spinner.component.css']
+})
+export class SpinnerComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html
index 2d7e4d86..84793260 100644
--- a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html
+++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html
@@ -1,9 +1,9 @@
-
-
- <h2 mat-dialog-title >{{data.title}}</h2>
- <div mat-dialog-content class="mt-4 text-offwhite" >
- {{data.message}}
- </div>
- <div mat-dialog-actions class="d-flex justify-content-center mt-4">
+<h2 mat-dialog-title>{{data.title}}</h2>
+<div mat-dialog-content class="mt-4 text-offwhite">
+ <form (keydown)="withEnterKey($event)">
+ {{data.message}}
+ </form>
+</div>
+<div mat-dialog-actions class="d-flex justify-content-center mt-4">
<button mat-raised-button cdkFocusInitial (click)="onOkClick()" color="basic">OK</button>
- </div> \ No newline at end of file
+</div> \ No newline at end of file
diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts
index e15f3c6f..7558b527 100644
--- a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts
+++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts
@@ -20,9 +20,11 @@ export class AlertDialogComponent {
//public dialog: MatDialog
) {}
+ withEnterKey(keyboardEvent: KeyboardEvent) {
+ if (keyboardEvent.code == "Enter" || keyboardEvent.code == "NumpadEnter")
+ this.onOkClick();
+ }
onOkClick(): void {
this.dialogRef.close();
}
-
-
}
diff --git a/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html
index 08c1f26b..fed8f8d7 100644
--- a/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html
+++ b/frontend/src/app/_modals/encoding-dialog/encoding-dialog.component.html
@@ -1,15 +1,17 @@
<h2 mat-dialog-title class="text-center">Enkodiranje svih kolona</h2>
<div mat-dialog-content class="mt-5 mb-4">
<p>Sve izabrane kolone biće enkodirane metodom:</p>
- <form (keydown)="withEnterKey($event)">
- <mat-form-field>
- <mat-select matNativeControl [(value)]="selectedEncodingType" cdkFocusInitial>
- <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option">
- {{ optionName }}
- </mat-option>
- </mat-select>
- </mat-form-field>
- </form>
+ <div class="d-flex justify-content-center">
+ <form (keydown)="withEnterKey($event)">
+ <mat-form-field>
+ <mat-select matNativeControl [(value)]="selectedEncodingType" cdkFocusInitial>
+ <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option">
+ {{ optionName }}
+ </mat-option>
+ </mat-select>
+ </mat-form-field>
+ </form>
+ </div>
</div>
<div mat-dialog-actions class="justify-content-center">
<button id="btnYes" mat-stroked-button color="basic" (click)="onYesClick()">Potvrdi</button>
diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css
index e99a1e1e..64d7bd21 100644
--- a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css
+++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.css
@@ -5,4 +5,8 @@
#btnNo {
color: gray;
+}
+
+::ng-deep.mat-dialog-content {
+ overflow: visible;
} \ No newline at end of file
diff --git a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html
index 7ab92d02..3332ef41 100644
--- a/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html
+++ b/frontend/src/app/_modals/missingvalues-dialog/missingvalues-dialog.component.html
@@ -2,8 +2,8 @@
<div mat-dialog-content class="mt-5 mb-4">
<form (keydown)="withEnterKey($event)">
<mat-radio-group [(ngModel)]="selectedMissingValuesOption" [ngModelOptions]="{standalone: true}">
- <mat-radio-button [value]="NullValueOptions.DeleteColumns" checked>Obriši sve kolone koje sadrže nedostajuće vrednosti</mat-radio-button>
- <mat-radio-button [value]="NullValueOptions.DeleteRows">Obriši sve redove koji sadrže nedostajuće vrednosti</mat-radio-button>
+ <mat-radio-button [value]="NullValueOptions.DeleteColumns" checked>Isključi sve kolone koje sadrže nedostajuće vrednosti</mat-radio-button>
+ <mat-radio-button [value]="NullValueOptions.DeleteRows" class="mt-1">Obriši sve redove koji sadrže nedostajuće vrednosti</mat-radio-button>
</mat-radio-group>
</form>
</div>
diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.html b/frontend/src/app/_modals/register-modal/register-modal.component.html
index 0c791a61..5182198c 100644
--- a/frontend/src/app/_modals/register-modal/register-modal.component.html
+++ b/frontend/src/app/_modals/register-modal/register-modal.component.html
@@ -33,6 +33,7 @@
<mat-icon matSuffix></mat-icon>
</mat-form-field>
<p *ngIf="wrongUsernameBool" class="wrong-creds">Unesite ispravno korisničko ime.</p>
+ <p *ngIf="usernameAlreadyExistsBool" class="wrong-creds">Uneto korisničko ime je zauzeto.</p>
</div>
<!--Email-->
<div>
@@ -42,6 +43,7 @@
<mat-icon matSuffix></mat-icon>
</mat-form-field>
<p *ngIf="wrongEmailBool" class="wrong-creds">Unesite ispravno e-mail adresu.</p>
+ <p *ngIf="emailAlreadyExistsBool" class="wrong-creds">Uneta e-mail adresa je zauzeta.</p>
</div>
<!-- Lozinka 1. -->
<div>
diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.ts b/frontend/src/app/_modals/register-modal/register-modal.component.ts
index a5c6ddc6..b1129668 100644
--- a/frontend/src/app/_modals/register-modal/register-modal.component.ts
+++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts
@@ -25,9 +25,11 @@ export class RegisterModalComponent implements OnInit {
wrongEmailBool: boolean = false;
wrongPass1Bool: boolean = false;
wrongPass2Bool: boolean = false;
+ usernameAlreadyExistsBool: boolean = false;
+ emailAlreadyExistsBool: boolean = false;
pattName: RegExp = /^[a-zA-ZšŠđĐčČćĆžŽ]+([ \-][a-zA-ZšŠđĐčČćĆžŽ]+)*$/;
- pattUsername: RegExp = /^[a-zA-Z0-9]{6,18}$/;
+ pattUsername: RegExp = /^[a-zA-Z0-9]{4,18}$/;
pattTwoSpaces: RegExp = / /;
pattEmail: RegExp = /^[a-zA-Z0-9]+([\.\-\+][a-zA-Z0-9]+)*\@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/;
pattPassword: RegExp = /.{6,30}$/;
@@ -59,7 +61,7 @@ export class RegisterModalComponent implements OnInit {
}
resetData() {
this.firstName = this.lastName = this.username = this.email = this.pass1 = this.pass2 = '';
- this.wrongFirstNameBool = this.wrongLastNameBool = this.wrongUsernameBool = this.wrongEmailBool = this.wrongPass1Bool = this.wrongPass2Bool = false;
+ this.wrongFirstNameBool = this.wrongLastNameBool = this.wrongUsernameBool = this.wrongEmailBool = this.wrongPass1Bool = this.wrongPass2Bool = this.usernameAlreadyExistsBool = this.emailAlreadyExistsBool = false;
this.password1Shown = false;
this.password2Shown = false;
}
@@ -160,10 +162,13 @@ export class RegisterModalComponent implements OnInit {
dateCreated:new Date()
}
+
this.authService.register(user)
.subscribe(
(response) => {
if (response == 'User added') {
+ this.usernameAlreadyExistsBool = false;
+ this.emailAlreadyExistsBool = false;
//nakon sto je registrovan, nek bude ulogovan
this.authService.login(this.username, this.pass1).subscribe((response) => {
@@ -174,12 +179,16 @@ export class RegisterModalComponent implements OnInit {
}, (error) => console.warn(error));
}
else if (response == 'Email Already Exists') {
- shared.openDialog("Greška!", "Nalog sa unetim email-om već postoji!");
- (<HTMLSelectElement>document.getElementById('email')).focus();
+ /*shared.openDialog("Greška!", "Nalog sa unetim email-om već postoji!");
+ (<HTMLSelectElement>document.getElementById('email')).focus();*/
+ this.usernameAlreadyExistsBool = false;
+ this.emailAlreadyExistsBool = true;
}
else if (response == 'Username Already Exists') {
- shared.openDialog("Greška!", "Nalog sa unetim korisničkim imenom već postoji!");
- (<HTMLSelectElement>document.getElementById('username-register')).focus();
+ /*shared.openDialog("Greška!", "Nalog sa unetim korisničkim imenom već postoji!");
+ (<HTMLSelectElement>document.getElementById('username-register')).focus();*/
+ this.emailAlreadyExistsBool = false;
+ this.usernameAlreadyExistsBool = true;
}
}
);
diff --git a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.css b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.css
index e69de29b..e99a1e1e 100644
--- a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.css
+++ b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.css
@@ -0,0 +1,8 @@
+#btnYes {
+ background-color: var(--offwhite);
+ color: var(--ns-bg-dark-100);
+}
+
+#btnNo {
+ color: gray;
+} \ No newline at end of file
diff --git a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html
index 06e74093..0470d395 100644
--- a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html
+++ b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html
@@ -1,8 +1,10 @@
-<h2 mat-dialog-title class="text-muted">{{data.title}}</h2>
-<div mat-dialog-content class="mt-4" style="color: rgb(81, 76, 76);">
- {{data.message}}
+<h2 mat-dialog-title class="text-center">{{data.title}}</h2>
+<div mat-dialog-content class="mt-5 mb-4 mx-1">
+ <form (keydown)="withEnterKey($event)">
+ {{data.message}}
+ </form>
</div>
<div mat-dialog-actions class="d-flex justify-content-center mt-4">
- <button mat-button cdkFocusInitial (click)="onYesClick()" style="background-color: lightgray;">Da</button>
- <button mat-button cdkFocusInitial (click)="onNoClick()" style="background-color: lightgray;">Ne</button>
+ <button id="btnYes" mat-stroked-button color="basic" (click)="onYesClick()">Da</button>
+ <button id="btnNo" mat-stroked-button (click)="onNoClick()">Ne</button>
</div> \ No newline at end of file
diff --git a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.ts b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.ts
index de1cdd4f..a7db1e7f 100644
--- a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.ts
+++ b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.ts
@@ -24,10 +24,13 @@ export class YesNoDialogComponent {
onNoClick(): void {
this.dialogRef.close();
}
- onYesClick():void{
+
+ withEnterKey(keyboardEvent: KeyboardEvent) {
+ if (keyboardEvent.code == "Enter" || keyboardEvent.code == "NumpadEnter")
+ this.onYesClick();
+ }
+ onYesClick():void {
this.data.yesFunction();
this.dialogRef.close();
}
-
-
}
diff --git a/frontend/src/app/_pages/archive/archive.component.css b/frontend/src/app/_pages/archive/archive.component.css
index e69de29b..3a8145dc 100644
--- a/frontend/src/app/_pages/archive/archive.component.css
+++ b/frontend/src/app/_pages/archive/archive.component.css
@@ -0,0 +1,3 @@
+.pro-center{
+ width: 80%;
+} \ No newline at end of file
diff --git a/frontend/src/app/_pages/archive/archive.component.html b/frontend/src/app/_pages/archive/archive.component.html
index f9cce56b..0c5fa8aa 100644
--- a/frontend/src/app/_pages/archive/archive.component.html
+++ b/frontend/src/app/_pages/archive/archive.component.html
@@ -1,5 +1,5 @@
<div class="d-flex flex-column align-items-center my-5">
- <app-folder [archive]="true" [startingTab]="TabType.MyExperiments"></app-folder>
+ <app-folder [archive]="true" [startingTab]="TabType.MyExperiments" class="pro-center"></app-folder>
<!--<div class="my-5" style="height: fit-content;">
<app-playlist [datasets]="publicDatasets"></app-playlist>
diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css
index 4c063f87..59e004e9 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.css
+++ b/frontend/src/app/_pages/experiment/experiment.component.css
@@ -36,32 +36,42 @@ mat-stepper {
display: flex;
flex-direction: column;
width: 100%;
+ height: 100%;
overflow-y: auto;
}
.step-content {
position: relative;
- width: 100%;
+ width: 98%;
+ min-height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
+ /*margin-bottom: 100px;*/
}
.step-content-inside {
width: 98%;
height: 98%;
- overflow-y: auto;
}
-.ekspName{
+
+.ekspName {
font-weight: bold;
font-size: large;
}
-
-.addedElement{
- color:var(--ns-accent);
+.addedElement {
+ color: var(--ns-accent);
}
+
.text-overflow {
overflow-wrap: break-word;
+ width: 170px;
+ display: inline-block;
+ text-align: left;
}
+
+.text-overflow-experiment-name {
+ overflow-wrap: break-word;
+} \ 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 a2ede838..17a6539d 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.html
+++ b/frontend/src/app/_pages/experiment/experiment.component.html
@@ -1,47 +1,52 @@
<div class="container-fluid p-0 text-offwhite holder" style="height: calc(100vh - 64px); text-align: center;">
<div class="d-flex flex-colum align-items-center sidenav">
<div>
- <div class="ekspName " style="width: 250px;">
- <div class="text-overflow" *ngIf="experiment.name=='exp1'" style="width: 250px;">
- Novi Eksperiment
- </div>
- <div class="text-overflow" *ngIf="experiment.name!='exp1'" style="width: 250px;">
- {{experiment.name}}
- </div>
+ <div class="ekspName " style="width: 250px;">
+ <div class="text-overflow-experiment-name" *ngIf="experiment.name=='exp1'" style="width: 250px;">
+ Novi Eksperiment
+ </div>
+ <div class="text-overflow-experiment-name" *ngIf="experiment.name!='exp1'" style="width: 250px;">
+ {{experiment.name}}
+ </div>
- </div>
- <mat-stepper orientation="vertical" (selectionChange)="changePage($event)">
- <mat-step>
- <!--editable="false"-->
- <ng-template matStepLabel>
- <span class="label" *ngIf="dataset==undefined">Izvor podataka</span>
- <span class="label addedElement" *ngIf="dataset!=undefined">{{dataset.name}}</span>
- </ng-template>
- <ng-template matStepContent>
- <p class="text-left">Izaberite vas izvor podataka</p>
- </ng-template>
- </mat-step>
- <mat-step>
- <ng-template matStepLabel>
- <span class="label addedElement" *ngIf="experiment.name!='exp1'">Predvideti:{{experiment.outputColumn}}</span>
- <span class="label" *ngIf="experiment.name=='exp1'">Odabir kolona</span>
- </ng-template>
- <ng-template matStepContent>
- <p class="text-left">Pripremite podatke i izaberite izlazne kolone</p>
- </ng-template>
- </mat-step>
- <mat-step>
- <ng-template matStepLabel>
- <span class="label addedElement" *ngIf="modelToTrain!=undefined">{{modelToTrain.name}}</span>
- <span class="label" *ngIf="modelToTrain==undefined">Treniranje</span>
- </ng-template>
- <p class="text-left">Odaberite parametre i trenirajte model</p>
- </mat-step>
- <mat-step>
- <ng-template matStepLabel><span class="label">Pregled rezultata treniranja</span></ng-template>
- <p class="text-left">Pregledajte tok treniranja i grafički prikaz rezultata</p>
- </mat-step>
- </mat-stepper>
+ </div>
+ <mat-stepper orientation="vertical" (selectionChange)="changePage($event)" [linear]="true">
+ <mat-step [completed]="this.step1">
+ <!--editable="false"-->
+ <ng-template matStepLabel>
+ <span class="label text-overflow" *ngIf="dataset==undefined">Izvor podataka </span>
+ <span class="label addedElement text-overflow" *ngIf="dataset!=undefined">{{dataset.name}}</span>
+ </ng-template>
+ <ng-template matStepContent>
+ <p class="text-left text-overflow">Izaberite izvor podataka</p>
+ </ng-template>
+ </mat-step>
+ <mat-step [completed]="experiment._id!=''">
+ <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>
+ </ng-template>
+ <ng-template matStepContent>
+ <p class="text-left text-overflow">Pripremite podatke i izaberite izlazne kolone</p>
+ </ng-template>
+ </mat-step>
+ <mat-step [completed]="this.step3">
+ <ng-template matStepLabel>
+ <span *ngIf="experiment._id==''" class="align-middle"><mat-icon>lock</mat-icon></span>
+ <span class="label addedElement text-overflow" *ngIf="modelToTrain!=undefined">{{modelToTrain.name}}</span>
+ <span class="label text-overflow" *ngIf="modelToTrain==undefined">Treniranje</span>
+ </ng-template>
+ <p class="text-left text-overflow">Odaberite parametre i trenirajte model</p>
+ </mat-step>
+ <mat-step [completed]="this.step4">
+ <ng-template matStepLabel>
+ <span *ngIf="!this.step3" class="align-middle"><mat-icon>lock</mat-icon></span>
+ <span class="label text-overflow align-middle">Pregled rezultata<br> treniranja</span>
+ </ng-template>
+ <p class="text-left text-overflow">Pregledajte tok treniranja i<br> grafički prikaz rezultata</p>
+ </mat-step>
+ </mat-stepper>
</div>
</div>
<div #stepsContainer class="steps-container">
@@ -50,20 +55,20 @@
<app-folder #folderDataset [type]="FolderType.Dataset" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyDatasets, TabType.PublicDatasets]" (okPressed)="goToPage(1)" (selectedFileChanged)="setDataset($event)"></app-folder>
</div>
</div>
- <div #steps id="step_2" class="step-content">
+ <div #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>
</div>
</div>
- <div #steps id="step_3" class="step-content">
+ <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>
</div>
- <div #steps id="step_4" class="step-content">
+ <div #steps id="step_4" class="step-content" *ngIf="step3">
<div class="step-content-inside">
<app-metric-view #metricView></app-metric-view>
</div>
</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 abf4b697..398321f2 100644
--- a/frontend/src/app/_pages/experiment/experiment.component.ts
+++ b/frontend/src/app/_pages/experiment/experiment.component.ts
@@ -1,4 +1,4 @@
-import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren, Input } from '@angular/core';
+import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren, Input, OnInit } from '@angular/core';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { MatStepper } from '@angular/material/stepper';
import Shared from 'src/app/Shared';
@@ -12,6 +12,9 @@ import Dataset from 'src/app/_data/Dataset';
import { ColumnTableComponent } from 'src/app/_elements/column-table/column-table.component';
import { SignalRService } from 'src/app/_services/signal-r.service';
import { MetricViewComponent } from 'src/app/_elements/metric-view/metric-view.component';
+import { ActivatedRoute, Router } from '@angular/router';
+import { DatasetsService } from 'src/app/_services/datasets.service';
+import { PredictorsService } from 'src/app/_services/predictors.service';
@Component({
selector: 'app-experiment',
@@ -32,7 +35,12 @@ export class ExperimentComponent implements AfterViewInit {
@ViewChild("folderModel") folderModel!: FolderComponent;
@ViewChild("metricView") metricView!: MetricViewComponent;
- constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService, private signalRService: SignalRService) {
+ step1: boolean = false;
+ step2: boolean = false;
+ step3: boolean = false;
+ step4: boolean = false;
+
+ constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService, private datasetsService: DatasetsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private route: ActivatedRoute) {
this.experiment = new Experiment("exp1");
}
@@ -49,6 +57,7 @@ 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;
}
}
@@ -82,11 +91,57 @@ export class ExperimentComponent implements AfterViewInit {
stat = stat.replace(/'/g, '"');
//console.log('JSON', this.trainingResult);
this.history.push(JSON.parse(stat));
- this.metricView.update(this.history);
+ this.metricView.update(this.history,this.modelToTrain.epochs);
}
});
}
+
+ this.route.queryParams.subscribe(params => {
+
+ let experimentId = this.route.snapshot.paramMap.get("id");
+ let predictorId = this.route.snapshot.paramMap.get("predictorId");
+ console.log("paramexp: ", experimentId, ", parampredictor: ", predictorId);
+ if (predictorId != null) {
+ this.predictorsService.getPredictor(predictorId!).subscribe((response) => {
+ let predictor = response;
+ //console.log("predictor: ", predictor);
+ this.experimentsService.getExperimentById(predictor.experimentId).subscribe((response) => {
+ this.experiment = response;
+ //console.log("experiment: ", this.experiment);
+ this.datasetsService.getDatasetById(this.experiment.datasetId).subscribe((response: Dataset) => {
+ this.dataset = response;
+ //console.log("dataset: ", this.dataset);
+ this.folderDataset.forExperiment = this.experiment;
+ this.folderDataset.selectFile(this.dataset); //sad 3. i 4. korak da se ucitaju
+
+ this.modelsService.getModelById(predictor.modelId).subscribe((response) => {
+ let model = response;
+ //console.log("model: ", model);
+ this.folderModel.formModel.newModel = model;
+ this.step3 = true;
+ let numOfEpochsArray = Array.from({length: model.epochs}, (_, i) => i + 1);
+ //console.log("metric view1:", this.metricView);
+ setTimeout(() => {
+ this.metricView.linechartComponent.update(numOfEpochsArray, predictor.metricsAcc, predictor.metricsLoss, predictor.metricsMae, predictor.metricsMse, predictor.metricsValAcc, predictor.metricsValLoss, predictor.metricsValMae, predictor.metricsValMse);
+ })
+ });
+ });
+ });
+ });
+ }
+ else if (experimentId != null) {
+ this.experimentsService.getExperimentById(experimentId).subscribe((response) => {
+ this.experiment = response;
+ this.datasetsService.getDatasetById(this.experiment.datasetId).subscribe((response: Dataset) => {
+ this.dataset = response;
+ this.folderDataset.forExperiment = this.experiment;
+ this.folderDataset.selectFile(this.dataset);
+ });
+ });
+ }
+
+ });
}
history: any[] = [];
@@ -148,16 +203,27 @@ export class ExperimentComponent implements AfterViewInit {
}
experimentChangedEvent() {
- this.folderModel.updateExperiment();
+ this.step2 = true;
+ setTimeout(() => {
+ this.folderModel.updateExperiment();
+ });
}
- setDataset(dataset: FolderFile) {
+ setDataset(dataset: FolderFile | null) {
+ if (dataset == null) {
+ this.columnTable.loaded = false;
+ this.dataset = undefined;
+ this.experiment.datasetId = '';
+ return;
+ }
const d = <Dataset>dataset;
this.experiment.datasetId = d._id;
this.dataset = d;
- this.columnTable.loadDataset(this.dataset);
-
+ this.step1 = true;
+ setTimeout(() => {
+ this.columnTable.loadDataset(d);
+ });
}
modelToTrain?: Model;
@@ -165,5 +231,6 @@ export class ExperimentComponent implements AfterViewInit {
setModel(model: FolderFile) {
const m = <Model>model;
this.modelToTrain = m;
+ this.step3 = true;
}
}
diff --git a/frontend/src/app/_pages/home/home.component.css b/frontend/src/app/_pages/home/home.component.css
index 906f5728..51f22320 100644
--- a/frontend/src/app/_pages/home/home.component.css
+++ b/frontend/src/app/_pages/home/home.component.css
@@ -11,10 +11,20 @@ h1 {
font-weight: 900 !important;
margin-top: 1rem;
margin-bottom: 2.5rem;
+ font-family: /*'Garamond',*/
+ 'Courier New', Courier, monospace;
}
.card {
margin: 2.5rem !important;
padding: 1.5rem;
width: 26rem !important;
+}
+
+a {
+ text-decoration: none;
+}
+
+.subtitle-class {
+ letter-spacing: 0.2rem;
} \ No newline at end of file
diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html
index 2825b3bf..534854b6 100644
--- a/frontend/src/app/_pages/home/home.component.html
+++ b/frontend/src/app/_pages/home/home.component.html
@@ -11,9 +11,9 @@
<div class="card shadowed bg-light text-light col-3 m-3" style="width: 18rem;">
<div class="card-body">
<mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">model_training</mat-icon>
- <h3 class="card-title my-2">Experimentiši</h3>
+ <h2 class="card-title my-2 subtitle-class">Eksperimentiši</h2>
<p class="card-text">
- U tri koraka <a class="stretched-link" routerLink="experiment">napravite novu neuronsku mrežu</a>. Koristite postojeće izvore podataka, modele, itd.
+ U tri koraka <a class="stretched-link text-light" routerLink="experiment">napravite novu neuronsku mrežu</a>. Koristite postojeće izvore podataka, modele, itd.
</p>
</div>
</div>
@@ -21,9 +21,9 @@
<div class="card-body">
<mat-icon width="48px" height="48px" style="font-size: 48px; margin-left: 50%; transform: translateX(-100%);">storage
</mat-icon>
- <h3 class="card-title my-2">Arhiva</h3>
+ <h2 class="card-title my-2 subtitle-class">Kolekcije</h2>
<p class="card-text">
- <a class="stretched-link" routerLink="archive">Upravljajte</a> izvorima podataka, eksperimentima, modelima, i rezultatima treniranja. Pogledajte podatke koje su podelili drugi korisnici.
+ <a class="stretched-link text-light" routerLink="archive">Upravljajte</a> izvorima podataka, eksperimentima, modelima i rezultatima treniranja. Pogledajte podatke koje su podelili drugi korisnici.
</p>
</div>
</div>
diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html
deleted file mode 100644
index 9b281239..00000000
--- a/frontend/src/app/_pages/my-models/my-models.component.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<div id="header">
- <h1>Moji modeli</h1>
-</div>
-<div id="wrapper">
- <div id="container" class="container p-5" style="background-color: rgba(255, 255, 255, 0.8); min-height: 100%;">
- <div class="row mt-3 mb-2 d-flex justify-content-center">
-
- <div class="col-sm-6" style="margin-bottom: 10px;">
- </div>
-
- <div class="row">
- <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let model of myModels">
- <app-item-model [model]="model"></app-item-model>
-
- <div class="panel-footer row"><!-- panel-footer -->
- <div class="col-xs-6 text-center">
- <div>
- <button type="button" class="btn btn-default btn-lg"style="min-width: 7rem;float: left;" (click)="useThisModel(model)" mat-raised-button color="primary">Koristi
- <span class="glyphicon glyphicon-search"></span>
- </button>
- <button (click)="deleteThisModel(model)" mat-raised-button color="warn" style="min-width: 7rem;float: right" ><mat-icon>delete</mat-icon></button>
-
-
- </div>
- </div>
- </div><!-- end panel-footer -->
-
-
-
- </div>
- </div>
- <div class="text-center" *ngIf="this.myModels.length == 0" >
- <h2>Nema rezultata</h2>
- </div>
- </div>
-
- </div>
-
-
-
-
-
- </div>
diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts
deleted file mode 100644
index d379fa69..00000000
--- a/frontend/src/app/_pages/my-models/my-models.component.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Component, OnInit } from '@angular/core';
-import { Router } from '@angular/router';
-import shared from 'src/app/Shared';
-import Model from 'src/app/_data/Model';
-import { ModelsService } from 'src/app/_services/models.service';
-
-@Component({
- selector: 'app-my-models',
- templateUrl: './my-models.component.html',
- styleUrls: ['./my-models.component.css']
-})
-export class MyModelsComponent implements OnInit {
- myModels: Model[] = [];
- //myModel: Model;
-
- constructor(private modelsS : ModelsService, private router : Router) {
-
-
-
- }
-
- ngOnInit(): void {
- this.getAllMyModels();
-
- }
-/*
- editModel(): void{
- this.modelsS.editModel().subscribe(m => {
- this.myModel = m;
-
- })
- }
-*/
-
-deleteThisModel(model: Model): void{
- shared.openYesNoDialog('Brisanje seta podataka','Da li ste sigurni da želite da obrišete model?',() => {
- this.modelsS.deleteModel(model).subscribe((response) => {
- this.getAllMyModels();
- }, (error) =>{
- if (error.error == "Model with name = {name} deleted") {
- shared.openDialog("Obaveštenje", "Greška prilikom brisanja modela.");
- }
- });
- });
-}
-
-
-useThisModel(model: Model): void{
-
- this.router.navigate(['/training'])
-
-}
- getAllMyModels(): void{
- this.modelsS.getMyModels().subscribe(m => {
- this.myModels = m;
- });
- }
-
-}
diff --git a/frontend/src/app/_pages/profile/profile.component.css b/frontend/src/app/_pages/profile/profile.component.css
index bbd4c9ba..48b1304b 100644
--- a/frontend/src/app/_pages/profile/profile.component.css
+++ b/frontend/src/app/_pages/profile/profile.component.css
@@ -22,4 +22,9 @@ mat-form-field {
.selectedPicture {
background-color: var(--ns-accent);
+}
+
+.mat-raised-button {
+ width: 180px !important;
+ height: 50px !important;
} \ No newline at end of file
diff --git a/frontend/src/app/_pages/profile/profile.component.html b/frontend/src/app/_pages/profile/profile.component.html
index 8d655513..39f33255 100644
--- a/frontend/src/app/_pages/profile/profile.component.html
+++ b/frontend/src/app/_pages/profile/profile.component.html
@@ -27,11 +27,11 @@
<div class="row gx-3 mb-3">
<!-- Form Group (password)-->
<div class="col-md-6">
- <small *ngIf="wrongPassBool" class="form-text danger-Text">Neispravna lozinka.</small>
<mat-form-field appearance="fill">
<mat-label>Važeća lozinka</mat-label>
<input matInput id="inputPassword" name="inputPassword" type="password" placeholder="" [(ngModel)]="this.oldPass">
</mat-form-field>
+ <small *ngIf="wrongPassBool" class="form-text danger-Text">Neispravna lozinka.</small>
<small *ngIf="wrongOldPassBool" class="form-text danger-Text">Pogrešan format.</small>
</div>
<!-- Form Group (new password)-->
@@ -50,7 +50,7 @@
<div class="col-md-6">
<div class="col text-center">
<!-- Save changes button-->
- <button mat-raised-button color="primary" (click)="savePasswordChanges()">Promeni lozinku</button>
+ <button mat-raised-button color="basic" (click)="savePasswordChanges()">Promeni lozinku</button>
</div>
</div>
<!-- Form Group (new password again)-->
@@ -122,7 +122,7 @@
<div class="container">
<div class="card-group">
<!--bootstrap card with 3 horizontal images-->
- <div class="row overflow-auto" style="max-height: 200px;">
+ <div class="row overflow-auto" style="max-height: 255px;">
<div class="card col-md-3" *ngFor="let picture of this.pictures" (click)="this.photoId = picture.photoId.toString()" [ngClass]="{'selectedPicture': this.photoId == picture.photoId.toString()}">
<img src="{{picture.path}}">
</div>
@@ -134,7 +134,7 @@
<div class="row mt-5">
<div class="col text-center">
<!-- Save changes button-->
- <button mat-raised-button color="primary" (click)="saveInfoChanges()">Sačuvaj izmene</button>
+ <button mat-raised-button color="basic" (click)="saveInfoChanges()">Sačuvaj izmene</button>
</div>
</div>
</form>
diff --git a/frontend/src/app/_pages/profile/profile.component.ts b/frontend/src/app/_pages/profile/profile.component.ts
index fdcd347c..aed8e40c 100644
--- a/frontend/src/app/_pages/profile/profile.component.ts
+++ b/frontend/src/app/_pages/profile/profile.component.ts
@@ -7,7 +7,8 @@ import { PICTURES } from 'src/app/_data/ProfilePictures';
import { Picture } from 'src/app/_data/ProfilePictures';
import shared from '../../Shared';
import { share } from 'rxjs';
-
+import { MatDialog } from '@angular/material/dialog';
+import { AlertDialogComponent } from 'src/app/_modals/alert-dialog/alert-dialog.component';
@Component({
selector: 'app-profile',
@@ -43,13 +44,13 @@ export class ProfileComponent implements OnInit {
wrongNewPass2Bool: boolean = false;
pattName: RegExp = /^[a-zA-ZšŠđĐčČćĆžŽ]+([ \-][a-zA-ZšŠđĐčČćĆžŽ]+)*$/;
- pattUsername: RegExp = /^[a-zA-Z0-9]{6,18}$/;
+ pattUsername: RegExp = /^[a-zA-Z0-9]{4,18}$/;
pattTwoSpaces: RegExp = / /;
pattEmail: RegExp = /^[a-zA-Z0-9]+([\.\-\+][a-zA-Z0-9]+)*\@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/;
pattPassword: RegExp = /.{6,30}$/;
- constructor(private userInfoService: UserInfoService, private authService: AuthService, private router: Router) { }
+ constructor(private userInfoService: UserInfoService, private authService: AuthService, private router: Router, public dialog?: MatDialog) { }
ngOnInit(): void {
this.userInfoService.getUserInfo().subscribe((response) => {
@@ -95,19 +96,24 @@ export class ProfileComponent implements OnInit {
if (this.user.username != editedUser.username) { //promenio username, ide logout
this.user = editedUser;
this.resetInfo();
- shared.openDialog("Obaveštenje", "Nakon promene korisničkog imena, moraćete ponovo da se ulogujete.");
- this.authService.logOut();
- this.router.navigate(['']);
- return;
+ const dialogRef = this.dialog?.open(AlertDialogComponent, {
+ width: '350px',
+ data: { title: "Obaveštenje", message: "Nakon promene korisničkog imena, moraćete ponovo da se ulogujete." }
+ });
+ dialogRef?.afterClosed().subscribe(res => {
+ this.authService.logOut();
+ this.router.navigate(['']);
+ return;
+ });
+ }
+ else {
+ shared.openDialog("Obaveštenje", "Podaci su uspešno promenjeni.");
+ this.user = editedUser;
+ this.resetInfo();
}
- shared.openDialog("Obaveštenje", "Podaci su uspešno promenjeni.");
- this.user = editedUser;
- this.resetInfo();
}, (error: any) =>{
if (error.error == "Username already exists!") {
shared.openDialog("Obaveštenje", "Ukucano korisničko ime je već zauzeto! Izaberite neko drugo.");
- //(<HTMLSelectElement>document.getElementById("inputUsername")).focus();
- //poruka obavestenja ispod inputa
this.resetInfo();
}
});
@@ -122,12 +128,10 @@ export class ProfileComponent implements OnInit {
if (this.newPass1 == '' && this.newPass2 == '') //ne zeli da promeni lozinku
return;
- //console.log("zeli da promeni lozinku");
if (this.newPass1 != this.newPass2) { //netacno ponovio novu lozinku
this.wrongNewPassBool = true;
this.resetNewPassInputs();
- //console.log("Netacno ponovljena lozinka");
return;
}
@@ -135,19 +139,23 @@ export class ProfileComponent implements OnInit {
this.userInfoService.changeUserPassword(passwordArray).subscribe((response: any) => {
//console.log("PROMENIO LOZINKU");
this.resetNewPassInputs();
- shared.openDialog("Obaveštenje", "Nakon promene lozinke, moraćete ponovo da se ulogujete.");
- this.authService.logOut();
- this.router.navigate(['']);
+ const dialogRef = this.dialog?.open(AlertDialogComponent, {
+ width: '350px',
+ data: { title: "Obaveštenje", message: "Nakon promene lozinke, moraćete ponovo da se ulogujete." }
+ });
+ dialogRef?.afterClosed().subscribe(res => {
+ this.authService.logOut();
+ this.router.navigate(['']);
+ return;
+ });
}, (error: any) => {
if (error.error == 'Wrong old password!') {
this.wrongPassBool = true;
- //(<HTMLSelectElement>document.getElementById("inputPassword")).focus();
return;
}
else if (error.error == 'Identical password!') {
shared.openDialog("Obaveštenje", "Stara i nova lozinka su identične.");
this.resetNewPassInputs();
- //(<HTMLSelectElement>document.getElementById("inputNewPassword")).focus();
return;
}
});
diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts
index 2211996f..2775613c 100644
--- a/frontend/src/app/_services/datasets.service.ts
+++ b/frontend/src/app/_services/datasets.service.ts
@@ -24,18 +24,35 @@ export class DatasetsService {
return this.http.post(`${Configuration.settings.apiURL}/dataset/add`, dataset, { headers: this.authService.authHeader() });
}
+ stealDataset(dataset: Dataset): Observable<any> {
+ return this.http.post(`${Configuration.settings.apiURL}/dataset/stealDs`, dataset, { headers: this.authService.authHeader() });
+ }
+
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){
+ return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${begin}/${end}`, { headers: this.authService.authHeader(), responseType: 'text' });
+ }
+ 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> {
return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${startRow}/${rowNum}`, { headers: this.authService.authHeader(), responseType: 'text' });
}
+ getDatasetById(datasetId: string): Observable<Dataset> {
+ return this.http.get<Dataset>(`${Configuration.settings.apiURL}/dataset/get/${datasetId}`, { headers: this.authService.authHeader() });
+ }
- editDataset(dataset: Dataset): Observable<Dataset> {
- return this.http.put<Dataset>(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader() });
+ 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' });
+ }
}
diff --git a/frontend/src/app/_services/experiments.service.ts b/frontend/src/app/_services/experiments.service.ts
index 29569fca..4b209ff0 100644
--- a/frontend/src/app/_services/experiments.service.ts
+++ b/frontend/src/app/_services/experiments.service.ts
@@ -20,7 +20,15 @@ export class ExperimentsService {
return this.http.get<Experiment[]>(`${Configuration.settings.apiURL}/experiment/getmyexperiments`, { headers: this.authService.authHeader() });
}
+ getExperimentById(id: string): Observable<Experiment> {
+ return this.http.get<Experiment>(`${Configuration.settings.apiURL}/experiment/get/${id}`, { headers: this.authService.authHeader() });
+ }
+
updateExperiment(experiment: Experiment): Observable<Experiment> {
return this.http.put<Experiment>(`${Configuration.settings.apiURL}/experiment/` + experiment._id, experiment, { headers: this.authService.authHeader() });
}
+
+ deleteExperiment(experiment: Experiment) {
+ return this.http.delete(`${Configuration.settings.apiURL}/experiment/` + experiment._id, { headers: this.authService.authHeader(), responseType: "text" });
+ }
}
diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts
index fc888556..2b8fe8f2 100644
--- a/frontend/src/app/_services/models.service.ts
+++ b/frontend/src/app/_services/models.service.ts
@@ -31,6 +31,10 @@ export class ModelsService {
addModel(model: Model): Observable<any> {
return this.http.post(`${Configuration.settings.apiURL}/model/add`, model, { headers: this.authService.authHeader() });
}
+
+ stealModel(model: Model): Observable<any> {
+ return this.http.post(`${Configuration.settings.apiURL}/model/stealModel`, model, { headers: this.authService.authHeader() });
+ }
addDataset(dataset: Dataset): Observable<any> {
return this.http.post(`${Configuration.settings.apiURL}/dataset/add`, dataset, { headers: this.authService.authHeader() });
}
@@ -47,7 +51,7 @@ export class ModelsService {
}
editModel(model: Model): Observable<Model> {
- return this.http.put<Model>(`${Configuration.settings.apiURL}/model/`, model, { headers: this.authService.authHeader() });
+ return this.http.put<Model>(`${Configuration.settings.apiURL}/model/` + model.name, model, { headers: this.authService.authHeader() });
}
deleteModel(model: Model) {
@@ -58,4 +62,8 @@ export class ModelsService {
return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/publicmodels`, { headers: this.authService.authHeader() });
}
+ getModelById(modelId: string): Observable<Model> {
+ return this.http.get<Model>(`${Configuration.settings.apiURL}/model/byid/${modelId}`, { headers: this.authService.authHeader() });
+ }
+
}
diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index f5f1ccae..d5552ce9 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -12,12 +12,14 @@ import { TestComponent } from './_pages/test/test.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: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } },
{ path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } },
- { path: 'sonja', component: ColumnTableComponent },
- { path: 'test', component: TestComponent, data: { title: 'Test' } }
+ { path: 'test', component: TestComponent, data: { title: 'Test' } },
+ { path: '**', redirectTo: '' }
];
@NgModule({
diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index e301b46f..5660f676 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -6,6 +6,7 @@ import { AuthService } from './_services/auth.service';
import { SignalRService } from './_services/signal-r.service';
import { HttpClient } from '@angular/common/http';
import Shared from './Shared';
+import { Chart } from 'chart.js';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
@@ -14,6 +15,117 @@ import Shared from './Shared';
export class AppComponent implements OnInit, AfterViewInit {
constructor(private router: Router, private titleService: Title, private authService: AuthService, private signalRService: SignalRService, private http: HttpClient) {
+ const getOrCreateTooltip = (chart: { canvas: { parentNode: { querySelector: (arg0: string) => any; appendChild: (arg0: any) => void; }; }; }) => {
+ let tooltipEl = chart.canvas.parentNode.querySelector('div');
+
+ if (!tooltipEl) {
+ tooltipEl = document.createElement('div');
+ tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';
+ tooltipEl.style.borderRadius = '3px';
+ tooltipEl.style.color = 'white';
+ tooltipEl.style.opacity = 1;
+ tooltipEl.style.pointerEvents = 'none';
+ tooltipEl.style.position = 'absolute';
+ tooltipEl.style.transform = 'translate(-50%, 0)';
+ tooltipEl.style.transition = 'all .1s ease';
+ tooltipEl.style.zIndex = 9000;
+
+ tooltipEl.classList.add("clearfix");
+
+ const table = document.createElement('table');
+ table.style.margin = '0px';
+
+ tooltipEl.appendChild(table);
+ chart.canvas.parentNode.appendChild(tooltipEl);
+ }
+
+ return tooltipEl;
+ };
+
+ const externalTooltipHandler = (context: { chart: any; tooltip: any; }) => {
+ // Tooltip Element
+ const { chart, tooltip } = context;
+ const tooltipEl = getOrCreateTooltip(chart);
+
+ // Hide if no tooltip
+ if (tooltip.opacity === 0) {
+ tooltipEl.style.opacity = 0;
+ return;
+ }
+
+ // Set Text
+ if (tooltip.body) {
+ const titleLines = tooltip.title || [];
+ const bodyLines = tooltip.body.map((b: { lines: any; }) => b.lines);
+
+ const tableHead = document.createElement('thead');
+
+ titleLines.forEach((title: string) => {
+ const tr = document.createElement('tr');
+ tr.style.borderWidth = '' + 0;
+
+ const th = document.createElement('th');
+ th.style.borderWidth = '' + 0;
+ const text = document.createTextNode(title);
+
+ th.appendChild(text);
+ tr.appendChild(th);
+ tableHead.appendChild(tr);
+ });
+
+ const tableBody = document.createElement('tbody');
+ bodyLines.forEach((body: string, i: string | number) => {
+ const colors = tooltip.labelColors[i];
+
+ const span = document.createElement('span');
+ span.style.background = colors.backgroundColor;
+ span.style.borderColor = colors.borderColor;
+ span.style.borderWidth = '2px';
+ span.style.marginRight = '10px';
+ span.style.height = '10px';
+ span.style.width = '10px';
+ span.style.display = 'inline-block';
+
+ const tr = document.createElement('tr');
+ tr.style.backgroundColor = 'inherit';
+ tr.style.borderWidth = '' + 0;
+
+ const td = document.createElement('td');
+ td.style.borderWidth = '' + 0;
+
+ const text = document.createTextNode(body);
+
+ td.appendChild(span);
+ td.appendChild(text);
+ tr.appendChild(td);
+ tableBody.appendChild(tr);
+ });
+
+ const tableRoot = tooltipEl.querySelector('table');
+
+ // Remove old children
+ while (tableRoot.firstChild) {
+ tableRoot.firstChild.remove();
+ }
+
+ // Add new children
+ tableRoot.appendChild(tableHead);
+ tableRoot.appendChild(tableBody);
+ }
+
+ const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;
+
+ // Display, position, and set styles for font
+ tooltipEl.style.opacity = 1;
+ tooltipEl.style.left = positionX + tooltip.caretX + 'px';
+ tooltipEl.style.top = positionY + tooltip.caretY + 'px';
+ tooltipEl.style.font = tooltip.options.bodyFont.string;
+ tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
+ };
+
+ Chart.defaults.plugins.tooltip.enabled = false;
+ Chart.defaults.plugins.tooltip.position = 'nearest';
+ Chart.defaults.plugins.tooltip.external = externalTooltipHandler;
}
ngAfterViewInit(): void {
}
@@ -40,7 +152,7 @@ export class AppComponent implements OnInit, AfterViewInit {
}
});
if (!this.authService.isAuthenticated()) {
- if(!this.authService.alreadyGuest())
+ if (!this.authService.alreadyGuest())
this.authService.addGuestToken();
}
this.signalRService.startConnection();
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index d44bf6ad..89d53115 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -52,6 +52,7 @@ import { DoughnutChartComponent } from './_elements/_charts/doughnut-chart/dough
import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component';
import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap';
import { MetricViewComponent } from './_elements/metric-view/metric-view.component';
+import { SpinnerComponent } from './_elements/spinner/spinner.component';
export function initializeApp(appConfig: Configuration) {
return () => appConfig.load();
@@ -91,7 +92,8 @@ export function initializeApp(appConfig: Configuration) {
HeatmapComponent,
MetricViewComponent,
LineChartComponent,
- SaveExperimentDialogComponent
+ SaveExperimentDialogComponent,
+ SpinnerComponent
],
imports: [
BrowserModule,