diff options
Diffstat (limited to 'frontend/src/app')
41 files changed, 1148 insertions, 688 deletions
diff --git a/frontend/src/app/_data/Dataset.ts b/frontend/src/app/_data/Dataset.ts index e8207718..7ae5c4ab 100644 --- a/frontend/src/app/_data/Dataset.ts +++ b/frontend/src/app/_data/Dataset.ts @@ -5,7 +5,6 @@ export default class Dataset extends FolderFile { constructor( name: string = 'Novi izvor podataka', public description: string = '', - public header: string[] = [], public fileId?: number, public extension: string = '.csv', public isPublic: boolean = false, @@ -14,7 +13,6 @@ export default class Dataset extends FolderFile { lastUpdated: Date = new Date(), public uploaderId: string = '', public delimiter: string = ',', - public hasHeader: boolean = true, public columnInfo: ColumnInfo[] = [], public rowCount: number = 0, @@ -29,12 +27,28 @@ export default class Dataset extends FolderFile { export class ColumnInfo { constructor( public columnName: string = '', + public columnType: ColumnType, public isNumber: boolean = false, public numNulls: number = 0, public uniqueValues?: string[], + public uniqueValuesCount?: number[], + public uniqueValuesPercent?: number[], public median?: number, public mean?: number, public min?: number, - public max?: number - ) { } + public max?: number, + public q1?: number, + public q3?: number, + ) { + /*if (isNumber) + this.columnType = ColumnType.numerical; + else + this.columnType = ColumnType.categorical;*/ + } + } + +export enum ColumnType { + categorical = "Kategorijski", + numerical = "Numerički" +}
\ No newline at end of file diff --git a/frontend/src/app/_data/Experiment.ts b/frontend/src/app/_data/Experiment.ts index ec966008..31816c19 100644 --- a/frontend/src/app/_data/Experiment.ts +++ b/frontend/src/app/_data/Experiment.ts @@ -5,6 +5,7 @@ export default class Experiment { constructor( public name: string = 'Novi eksperiment', public description: string = '', + public type: ProblemType = ProblemType.Regression, public datasetId: string = '', public inputColumns: string[] = [], public outputColumn: string = '', @@ -14,10 +15,7 @@ export default class Experiment { public lastUpdated: Date = new Date(), public modelIds: string[] = [], - // Test set settings - public randomOrder: boolean = true, - public randomTestSet: boolean = true, - public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U + public encodings: ColumnEncoding[] = []//[{columnName: "", columnEncoding: Encoding.Label}] ) { } diff --git a/frontend/src/app/_data/FolderFile.ts b/frontend/src/app/_data/FolderFile.ts index a79eeac5..c228f25e 100644 --- a/frontend/src/app/_data/FolderFile.ts +++ b/frontend/src/app/_data/FolderFile.ts @@ -9,5 +9,6 @@ export class FolderFile { export enum FolderType { Dataset, - Model + Model, + Experiment }
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index 6281748c..185e2257 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -23,7 +23,12 @@ export default class Model extends FolderFile { public epochs: number = 5, // TODO add to add-model form public inputColNum: number = 5, public learningRate: LearningRate = LearningRate.LR1, - public layers: Layer[] = [new Layer()] + 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 ) { super(name, dateCreated, lastUpdated); 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 3faa4794..d6f4b6ec 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 @@ -27,8 +27,8 @@ export class BoxPlotComponent implements AfterViewInit { labels: ['January'/*, 'February', 'March', 'April', 'May', 'June', 'July'*/], datasets: [{ label: 'Dataset 1', - backgroundColor: 'rgba(0, 65, 101, 1.0)', - borderColor: '#0063AB', + backgroundColor: '#0063AB', + borderColor: '#dfd7d7', borderWidth: 1, outlierColor: '#999999', scaleFontColor: '#0063AB', @@ -78,7 +78,7 @@ export class BoxPlotComponent implements AfterViewInit { scales : { x: { ticks: { - color: 'rgba(0, 65, 101, 1.0)' + color: '#dfd7d7' }, grid: { color: "rgba(0, 99, 171, 0.5)" @@ -88,7 +88,7 @@ export class BoxPlotComponent implements AfterViewInit { min: -50, max: 200, ticks: { - color: 'rgba(0, 65, 101, 1.0)' + color: '#dfd7d7' }, grid: { color: "rgba(0, 99, 171, 0.5)" 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 c8f406f4..7f18256a 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,5 +1,3 @@ -<div class="chart-wrapper"> - <canvas id="myChart"> - - </canvas> -</div>
\ No newline at end of file + + <canvas id="myChart" style="width: 100%; height: 530px;"> + </canvas>
\ 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 49558025..34df38bc 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 } from '@angular/core'; +import { Component, AfterViewInit, ViewChild } from '@angular/core'; import { Chart } from 'chart.js'; @Component({ @@ -9,12 +9,12 @@ import { Chart } from 'chart.js'; export class LineChartComponent implements AfterViewInit { - dataAcc: number[] = []; - dataMAE: number[] = []; - dataMSE: number[] = []; - dataLOSS: number[] = []; + dataAcc: number[] = [2,3,5,5,6,7,8,8,4,6,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]; + dataMAE: number[] = [2,3,5,5,6,7,8,8,4,6,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]; + dataMSE: number[] = [2,3,5,5,6,7,8,8,4,6,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]; + dataLOSS: number[] =[2,3,5,5,6,7,8,8,4,6,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]; - dataEpoch: number[] = []; + dataEpoch: number[] = [2,3,5,5,6,7,8,8,4,6,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]; constructor() { /*let i = 0; @@ -77,11 +77,28 @@ export class LineChartComponent implements AfterViewInit { }, options: { scales: { - y: { - beginAtZero: true - } + x:{ + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + }, + y: { + beginAtZero: true, + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + } + } - } + + + } } ); } diff --git a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css index 5735217e..005cb692 100644 --- a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.css @@ -1,6 +1,4 @@ #divScatterChart{ - background-color: beige; - display: block; - width: 400px; - height: 200px; + + display: block; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html index 2b30fe1f..ef41775a 100644 --- a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.html @@ -1,4 +1,4 @@ -<p>Scatter chart:</p> -<div id="divScatterChart"> - <canvas id="ScatterCharts"> </canvas> + +<div id="divScatterChart" style="width: 100%;height: 100%;"> + <canvas id="ScatterCharts" style="width: 100%;height: 280px;"> </canvas> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts index 9dfef4c3..12795c70 100644 --- a/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts +++ b/frontend/src/app/_elements/_charts/scatterchart/scatterchart.component.ts @@ -24,15 +24,32 @@ export class ScatterchartComponent implements OnInit { {x: 15, y: 5}, {x: 16, y: 3}, {x: 17, y: 2}], - backgroundColor: 'rgb(255, 99, 132)' + borderColor: 'white', }] }, options: { scales: { + x:{ + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } + }, y: { - beginAtZero: true + beginAtZero: true, + ticks: { + color: 'white' + }, + grid: { + color: "rgba(0, 99, 171, 0.5)" + } } + } + + } }); } diff --git a/frontend/src/app/_elements/column-table/column-table.component.css b/frontend/src/app/_elements/column-table/column-table.component.css index 108efb32..0477b7be 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.css +++ b/frontend/src/app/_elements/column-table/column-table.component.css @@ -3,7 +3,6 @@ table.fixed { display: block; overflow-x: auto; white-space: nowrap; - border: 1px solid var(--ns-primary-50); font-size: 12px; border-radius: 4px; } @@ -18,7 +17,6 @@ table.fixed td { max-width: 200px; min-width: 200px; vertical-align: middle; - background-color: var(--ns-bg-dark-100); margin: 4px; } @@ -27,13 +25,12 @@ table.fixed th { max-width: 120px; min-width: 120px; vertical-align: middle; - background-color: var(--ns-bg-dark-100); + background-color: var(--ns-primary-50); font-size: 14px; } table.fixed th:first-child { text-align: center; - background-color: var(--ns-primary-25); } .columnNames { @@ -58,8 +55,8 @@ mat-slider { } #missingValuesHeader { - font-size: 12px; - line-height: 110% !important; + font-size: 13px; + line-height: 140% !important; } .verticalAlign { @@ -90,7 +87,7 @@ table ::ng-deep .mat-form-field-wrapper { } .no-pad { - padding: 1px; + padding: 2px; margin: 0; } @@ -107,7 +104,7 @@ table ::ng-deep .mat-form-field-wrapper { } .graphic-class { - background-color: white !important; + opacity: 0.5; } @@ -192,8 +189,9 @@ table ::ng-deep .mat-form-field-wrapper { } .hidden { - visibility: hidden; - height: 1px; + /*visibility: hidden; + height: 1px;*/ + display: none; } .bottom-button { @@ -220,4 +218,49 @@ table ::ng-deep .mat-form-field-wrapper { .long { height: 3rem; +} + +.col-disabled { + background-color: rgb(0, 45, 69); +} + +.text-disabled { + color: gray; +} + +.header-disabled { + color: gray; +} + +.menu-disabled { + pointer-events: none; + opacity: .5; +} + +col:not(.col-disabled) { + background-color: var(--ns-bg-dark-100); +} + +.col-first { + background-color: rgb(1, 56, 86) !important; +} + + +/* mat-icon rotate */ + +.rotate { + animation: rotation 3s infinite linear; +} + +.rotate:hover { + cursor: pointer; +} + +@keyframes rotation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(359deg); + } }
\ No newline at end of file diff --git a/frontend/src/app/_elements/column-table/column-table.component.html b/frontend/src/app/_elements/column-table/column-table.component.html index 53cb3551..543a0018 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.html +++ b/frontend/src/app/_elements/column-table/column-table.component.html @@ -1,235 +1,231 @@ -<div id="tabs"> - <div class="folder-tab p-1 rounded-top" *ngFor="let tab of tabs; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedTab.index == i, 'hover-tab' : hoveringOverTab?.index == i}"> - <a class="m-1 stretched-link tab-link" (click)="selectTab(i)" (mouseenter)="hoverOverTab(i)" (mouseleave)="hoverOverTab(-1)"> +<div *ngIf="loaded"> + <div id="tabs"> + <div class="folder-tab p-1 rounded-top" *ngFor="let tab of tabs; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedTab.index == i, 'hover-tab' : hoveringOverTab?.index == i}"> + <a class="m-1 stretched-link tab-link" (click)="selectTab(i)" (mouseenter)="hoverOverTab(i)" (mouseleave)="hoverOverTab(-1)"> {{tab.name}} </a> - </div> + </div> + <!-- <button mat-button class="p-1 folder-tab-end rounded-top"> Kolone <mat-icon>keyboard_double_arrow_down</mat-icon> - <!--meni ovde--> </button> -</div> -<div id="folder-table" *ngIf="dataset && experiment"> - <!--<div [ngSwitch]="tabToDisplay">--> - <div id="divTable"> - - <div [ngClass]="{'hidden': tabToDisplay != Table.Data}"> - <table class="table text-offwhite fixed bg-blur"> - <thead> - <tr> - <th>#</th> - <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index"> - <div class="cell-align"> - #{{i + 1}} {{colInfo.columnName}} - <mat-checkbox color="primary" checked (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> - </div> - </th> - </tr> - </thead> - <tbody> - <tr *ngFor="let row of tableData; let i = index"> - <th>#{{i}}</th> - <td *ngFor="let col of row; let j = index"> - <div class="text-overflow"> - {{col}} - </div> - </td> - </tr> - </tbody> - </table> - </div> - - <div [ngClass]="{'hidden': tabToDisplay != Table.CorrelationMatrix}"> - <table class="table text-offwhite fixed bg-blur"> - <thead> - <tr> - <th>Naziv</th> - <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index"> - #{{i + 1}} {{colInfo.columnName}} - </th> - </tr> - </thead> - <tbody> - <tr *ngFor="let colInfo of dataset.columnInfo; let i = index"> - <th> - <div class="text-left"> - {{colInfo.columnName}} - </div> - </th> - <td *ngFor="let colInfo of dataset.columnInfo; let j = index"> - <div class="text-overflow"> - 0.1 - </div> - </td> - </tr> - </tbody> - </table> - </div> - - <div [ngClass]="{'hidden': tabToDisplay != Table.Columns}"> - <table class="table text-offwhite fixed bg-blur"> - <thead> - <tr> - <th>Naziv</th> - <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index"> - <div class="cell-align"> - #{{i + 1}} {{colInfo.columnName}} - <mat-checkbox color="primary" checked (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> - </div> - </th> - </tr> - </thead> - <tbody> - <tr> - <th>Tip</th> - <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix"> - <mat-form-field> - <mat-select matNativeControl [(value)]="colInfo.isNumber"> - <mat-option [value]="false">Kategorijski</mat-option> - <mat-option [value]="true">Numerički</mat-option> - </mat-select> - </mat-form-field> - </td> - </tr> - <tr class="graphics-row"> - <th class="no-pad border-bottom">Grafik</th> - <td class="graphic-class no-pad" *ngFor="let colInfo of dataset.columnInfo; let i = index"> - <app-box-plot *ngIf="colInfo.isNumber" [width]="150" [height]="150"></app-box-plot> - <app-pie-chart *ngIf="!colInfo.isNumber" [width]="150" [height]="150"></app-pie-chart> - </td> - </tr> - <tr> - <th class="brighter">Statistika</th> - <td *ngFor="let colInfo of dataset.columnInfo; let i = index"> - <span *ngIf="colInfo.isNumber"> - Mean: {{colInfo.mean}}<br> - Median: {{colInfo.median}}<br> - Min: {{colInfo.min}}<br> - Max: {{colInfo.max}}<br> - <!-- TODO na ML-u: Q1 i Q3 u statistici - Q1: {{colInfo.q1}}<br> - Q3: {{colInfo.q3}}<br> - --> - </span> - <div class="text-overflow" *ngIf="!colInfo.isNumber"> - <span *ngFor="let uniqueValue of colInfo.uniqueValues | slice:0:6; let i = index"> - {{uniqueValue}}<br><!-- TODO na ML-u: broj ponavljanja unique values-a u zagradi nek pise --> - </span> - </div> - </td> - </tr> - <tr style="padding: 0"> - <th class="brighter cell-align long" (click)="openEncodingDialog()"> - <span class="verticalAlign">Enkodiranje</span> - <span class="material-icons-round verticalAlign">settings</span> - </th> - <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix"> - <mat-form-field> - <mat-select matNativeControl [(value)]="experiment.encodings[i].encoding"> - <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - </td> - </tr> - <tr> - <th class="brighter cell-align" (click)="openMissingValuesDialog()"> - <div id="missingValuesHeader">Regulisanje<br>nedostajućih<br>vrednosti<br></div> - <span class="material-icons-round">settings</span> - </th> - <td *ngFor="let colInfo of dataset.columnInfo; let i = index"> - - <button class="w-100" mat-raised-button [matMenuTriggerFor]="menu" id="main_{{colInfo.columnName}}" #nullValMenu> - <div class="cell-align"> - {{nullValOption[i]}} - <mat-icon>arrow_drop_down</mat-icon> - </div> - </button> - <mat-menu #menu="matMenu"> - <button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteColumns, i)" value={{colInfo.columnName}}>Obriši kolonu</button> - <button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteRows, i)" value={{colInfo.columnName}}>Obriši redove</button> - <button mat-menu-item [matMenuTriggerFor]="fillWith">Popuni sa ____</button> - </mat-menu> - - <mat-menu #fillWith="matMenu"> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button> - <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.min}}>Min ({{colInfo.min}})</button> - - <button *ngIf="!colInfo.isNumber" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button> - - <button mat-menu-item [matMenuTriggerFor]="replaceWith">Unesi vrednost...</button> - </mat-menu> - - <mat-menu #uniques="matMenu"> - <button mat-menu-item *ngFor="let uniqueValue of colInfo.uniqueValues" (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{uniqueValue}}>{{uniqueValue}}</button> - </mat-menu> - - <mat-menu #replaceWith="matMenu"> - <input type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value]> - <button [disabled]="getValue(colInfo.columnName) == ''" mat-menu-item value={{getValue(colInfo.columnName)}} (click)="MissValsReplaceClicked($event, colInfo.columnName, i)">Potvrdi unos</button> - </mat-menu> - - </td> - </tr> - <!--<tr class="row-height" *ngFor="let row of tableData; let i = index"> - <th *ngIf="i == 0" [attr.rowspan]="tableData!.length">Vrednosti</th> - - - <td class="text-center" *ngFor="let col of row; let j = index"> - <div class="text-overflow"> - {{col}} - </div> - </td> - </tr>--> - </tbody> - </table> - </div> + --> </div> -</div> - -<div class="container-fluid text-offwhite belowColumn mt-3"> - <div class="ns-row"> - <div class="ns-col slider rounded" style="border:1px solid var(--ns-primary)"> + <div id="folder-table" *ngIf="dataset && experiment"> + <!--<div [ngSwitch]="tabToDisplay">--> + <div id="divTable"> + + <div [ngClass]="{'hidden': tabToDisplay != Table.Data}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>#</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let row of tableData; let i = index"> + <th>#{{i}}</th> + <td *ngFor="let col of row; let j = index" [ngClass]="{'text-disabled' : !columnsChecked[j]}"> + <div class="text-overflow"> + {{col}} + </div> + </td> + </tr> + </tbody> + </table> + </div> - <div 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 [ngClass]="{'hidden': tabToDisplay != Table.CorrelationMatrix}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>Kolona</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr *ngFor="let colInfo of dataset.columnInfo; let i = index"> + <th [ngClass]="{'header-disabled col-disabled' : !columnsChecked[i]}"> + <div class="text-left"> + {{colInfo.columnName}} + </div> + </th> + <td *ngFor="let colInfo of dataset.columnInfo; let j = index" [ngClass]="{'text-disabled col-disabled' : !columnsChecked[j] || !columnsChecked[i]}"> + <div class="text-overflow"> + 0.1 + </div> + </td> + </tr> + </tbody> + </table> + </div> - </div> - <div class="ns-col slider rounded" style="border:1px solid var(--ns-primary);margin-left: 10px;"> - <div class="text-center text-offwhite justify-content-center align-items-center"> - <mat-checkbox class="pt-4" color="accent">Nasumični redosled podataka</mat-checkbox> + <div [ngClass]="{'hidden': tabToDisplay != Table.Columns}"> + <table class="table text-offwhite fixed bg-blur"> + <colgroup> + <col class="col-first"> + <col *ngFor="let column of dataset.columnInfo; let i = index" [ngClass]="{'col-disabled' : !experiment.inputColumns.includes(column.columnName)}"> + </colgroup> + <thead> + <tr> + <th>Naziv</th> + <th class="columnNames" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'header-disabled' : !columnsChecked[i]}"> + <div class="cell-align"> + #{{i + 1}} {{colInfo.columnName}} + <mat-checkbox color="primary" [(ngModel)]="columnsChecked[i]" (change)="changeInputColumns($event, colInfo.columnName)"></mat-checkbox> + </div> + </th> + </tr> + </thead> + <tbody> + <tr> + <th>Tip</th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + <p class="verticalAlign text-left" style="font-size:13px;" *ngIf="!colInfo.isNumber">Kategorijski</p> + <mat-form-field *ngIf="colInfo.isNumber"> + <mat-select matNativeControl [(value)]="colInfo.columnType" [disabled]="!columnsChecked[i]" (selectionChange)="columnTypeChanged(colInfo.columnName);"> + <mat-option [value]="ColumnType.categorical">Kategorijski</mat-option> + <mat-option [value]="ColumnType.numerical">Numerički</mat-option> + </mat-select> + </mat-form-field> + </td> + </tr> + <tr class="graphics-row"> + <th class="no-pad">Grafik</th> + <td class="no-pad" *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'graphic-class' : !columnsChecked[i]}"> + <app-box-plot *ngIf="colInfo.columnType == ColumnType.numerical" [width]="150" [height]="150"></app-box-plot> + <app-pie-chart *ngIf="colInfo.columnType == ColumnType.categorical" [width]="150" [height]="150"></app-pie-chart> + </td> + </tr> + <tr> + <th class="border-bottom">Statistika</th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'text-disabled' : !columnsChecked[i]}" class="text-left"> + <span *ngIf="colInfo.columnType == ColumnType.numerical"> + Mean: {{colInfo.mean}}<br> + Median: {{colInfo.median}}<br> + Min: {{colInfo.min}}<br> + Max: {{colInfo.max}}<br> + Q1: {{colInfo.q1}}<br> + Q3: {{colInfo.q3}}<br> + </span> + <div class="text-overflow" *ngIf="colInfo.columnType == ColumnType.categorical && colInfo.uniqueValuesPercent"> + <span *ngFor="let uniqueValue of colInfo.uniqueValues | slice:0:6; let i = index"> + ({{(colInfo.uniqueValuesPercent[i] * 100).toFixed(2)}}%) {{uniqueValue}}<br> + </span> + </div> + </td> + </tr> + <tr style="padding: 0"> + <th class="brighter cell-align long" (click)="openEncodingDialog()"> + <span class="verticalAlign">Enkodiranje</span> + <span class="material-icons-round verticalAlign rotate">settings</span> + </th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" class="pad-fix" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + <mat-form-field> + <mat-select matNativeControl [(value)]="experiment.encodings[i].encoding" [disabled]="!columnsChecked[i]" (selectionChange)="columnTableChangeDetected()"> + <mat-option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </td> + </tr> + <tr> + <th class="brighter cell-align" (click)="openMissingValuesDialog()"> + <div id="missingValuesHeader">Nedostajuće<br>vrednosti<br></div> + <span class="material-icons-round rotate">settings</span> + </th> + <td *ngFor="let colInfo of dataset.columnInfo; let i = index" [ngClass]="{'text-disabled' : !columnsChecked[i]}"> + + <div *ngIf="colInfo.numNulls > 0"> + <button class="w-100" mat-raised-button [ngClass]="{ 'menu-disabled' : !columnsChecked[i]}" [matMenuTriggerFor]="menu" id="main_{{colInfo.columnName}}" #nullValMenu> + <div class="cell-align"> + {{nullValOption[i]}} + <mat-icon>arrow_drop_down</mat-icon> + </div> + </button> + <mat-menu #menu="matMenu"> + <!--<button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteColumns, i)" value={{colInfo.columnName}}>Obriši kolonu</button>--> + <button mat-menu-item (click)="MissValsDeleteClicked($event, NullValueOptions.DeleteRows, i)" value={{colInfo.columnName}}>Obriši redove ({{colInfo.numNulls}})</button> + <button mat-menu-item [matMenuTriggerFor]="fillWith">Popuni sa</button> + </mat-menu> + + <mat-menu #fillWith="matMenu"> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.mean}}>Mean ({{colInfo.mean}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.median}}>Median ({{colInfo.median}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.max}}>Max ({{colInfo.max}})</button> + <button *ngIf="colInfo.isNumber" mat-menu-item (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{colInfo.min}}>Min ({{colInfo.min}})</button> + + <button *ngIf="!colInfo.isNumber" mat-menu-item [matMenuTriggerFor]="uniques">Najčešće vrednosti</button> + + <button mat-menu-item [matMenuTriggerFor]="replaceWith">Unesi vrednost...</button> + </mat-menu> + + <mat-menu #uniques="matMenu"> + <button mat-menu-item *ngFor="let uniqueValue of colInfo.uniqueValues" (click)="MissValsReplaceClicked($event, colInfo.columnName, i)" value={{uniqueValue}}>{{uniqueValue}}</button> + </mat-menu> + + <mat-menu #replaceWith="matMenu"> + <input type="text" id={{colInfo.columnName}} mat-menu-item placeholder="Unesi vrednost..." [value]> + <button [disabled]="getValue(colInfo.columnName) == ''" mat-menu-item value={{getValue(colInfo.columnName)}} (click)="MissValsReplaceClicked($event, colInfo.columnName, i)">Potvrdi unos</button> + </mat-menu> + </div> + <div *ngIf="colInfo.numNulls == 0" class="text-left"> + Nema nedostajućih vrednosti. + </div> + </td> + </tr> + </tbody> + </table> </div> </div> + </div> - <div class="break-2"></div> - - <div class="ns-col rounded"> - <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> - <mat-label>Tip problema</mat-label> - <mat-select value="ToDo1"> - <mat-option value="ToDo1">Regresioni</mat-option> - <mat-option value="ToDo2">Binarni-Klasifikacioni</mat-option> - <mat-option value="ToDo3">Multi-Klasifikacioni</mat-option> - </mat-select> - </mat-form-field> - </div> - <div class="ns-col rounded"> - <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> - <mat-label>Izlazna kolona</mat-label> - <mat-select> - <mat-option *ngFor="let item of dataset?.columnInfo" [value]="item.columnName">{{item.columnName}}</mat-option> - </mat-select> - </mat-form-field> - </div> - <div class="break-1"></div> - <div class="ns-col d-flex align-items-center justify-content-center"> - <button mat-button (click)="ok()" class="bottom-button text-offwhite rounded-bottom"> + <div *ngIf="dataset && experiment" class="container-fluid text-offwhite belowColumn mt-3"> + <div class="ns-row"> + <div class="break-2"></div> + + <div class="ns-col rounded"> + <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> + <mat-label>Izlazna kolona</mat-label> + <mat-select [(value)]="experiment.outputColumn" (selectionChange)="changeOutputColumn(this.experiment.inputColumns[0])"> + <mat-option *ngFor="let inputColumn of experiment.inputColumns" [value]="inputColumn">{{inputColumn}}</mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="ns-col rounded"> + <mat-form-field appearance="fill" class="align-items-center justify-content-center pt-3 w-100"> + <mat-label>Tip problema</mat-label> + <mat-select [(value)]="experiment.type"> + <mat-option [value]="ProblemType.Regression">Regresioni</mat-option> + <mat-option [value]="ProblemType.BinaryClassification">Binarni-klasifikacioni</mat-option> + <mat-option [value]="ProblemType.MultiClassification">Multi-klasifikacioni</mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="break-1"></div> + <div class="ns-col d-flex align-items-center justify-content-center"> + <button mat-button (click)="saveExperiment()" class="bottom-button text-offwhite rounded-bottom"> <div class="f-row" style="justify-content: space-around;"> <div>Potvrdi</div> <div class="icon-double pt-1"> @@ -238,6 +234,7 @@ </div> </div> </button> + </div> </div> </div> </div>
\ No newline at end of file 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 9cabf190..c3d4f206 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.ts +++ b/frontend/src/app/_elements/column-table/column-table.component.ts @@ -1,13 +1,15 @@ import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChildren } from '@angular/core'; -import Dataset from 'src/app/_data/Dataset'; +import Dataset, { ColumnType } from 'src/app/_data/Dataset'; import Experiment, { ColumnEncoding, Encoding, NullValReplacer, NullValueOptions } from 'src/app/_data/Experiment'; import { DatasetsService } from 'src/app/_services/datasets.service'; import { EncodingDialogComponent } from 'src/app/_modals/encoding-dialog/encoding-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import { MissingvaluesDialogComponent } from 'src/app/_modals/missingvalues-dialog/missingvalues-dialog.component'; -import { MatSliderChange } from '@angular/material/slider'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; +import { ProblemType } from 'src/app/_data/Model'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; +import { SaveExperimentDialogComponent } from 'src/app/_modals/save-experiment-dialog/save-experiment-dialog.component'; @Component({ selector: 'app-column-table', @@ -17,43 +19,69 @@ import { CsvParseService } from 'src/app/_services/csv-parse.service'; export class ColumnTableComponent implements AfterViewInit { @Input() dataset?: Dataset; - @Input() experiment?: Experiment; + @Input() experiment!: Experiment; @ViewChildren("nullValMenu") nullValMenus!: ElementRef[]; @Output() okPressed: EventEmitter<string> = new EventEmitter(); + @Output() columnTableChanged = new EventEmitter(); + Object = Object; Encoding = Encoding; NullValueOptions = NullValueOptions; + ColumnType = ColumnType; + ProblemType = ProblemType; tableData?: any[][]; nullValOption: string[] = []; - testSetDistribution: number = 70; - constructor(private datasetService: DatasetsService, public csvParseService: CsvParseService, public dialog: MatDialog) { + columnsChecked: boolean[] = []; //niz svih kolona + loaded: boolean = false; + + + constructor(private datasetService: DatasetsService, private experimentService: ExperimentsService, public csvParseService: CsvParseService, public dialog: MatDialog) { //ovo mi nece trebati jer primam dataset iz druge komponente } - ngAfterViewInit(): void { - this.datasetService.getMyDatasets().subscribe((datasets) => { - this.dataset = datasets[0]; - this.experiment = new Experiment(); + loadDataset(dataset: Dataset) { + this.dataset = dataset; - console.log(datasets); - for (let i = 0; i < this.dataset?.columnInfo.length; i++) { - this.experiment?.inputColumns.push(this.dataset.columnInfo[i].columnName); - } - this.resetColumnEncodings(Encoding.Label); - this.setDeleteColumnsForMissingValTreatment(); + this.setColumnTypeInitial(); + + this.dataset.columnInfo.forEach(column => { + this.columnsChecked.push(true); + }); + + for (let i = 0; i < this.dataset?.columnInfo.length; i++) { + this.experiment.inputColumns.push(this.dataset.columnInfo[i].columnName); + } + this.experiment.outputColumn = this.experiment.inputColumns[0]; + this.resetColumnEncodings(Encoding.Label); + this.setDeleteRowsForMissingValTreatment(); - this.nullValOption = [].constructor(this.dataset.columnInfo.length).fill('Obriši redove'); + this.nullValOption = []; + this.dataset.columnInfo.forEach(colInfo => { + this.nullValOption.push(`Obriši redove (${colInfo.numNulls})`); + }); - this.datasetService.getDatasetFilePartial(this.dataset.fileId, 0, 10).subscribe((response: string | undefined) => { - if (response && this.dataset != undefined) { - this.tableData = this.csvParseService.csvToArray(response, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter.toString() == "") ? "," : this.dataset.delimiter); - } - }); + this.datasetService.getDatasetFilePartial(this.dataset.fileId, 0, 10).subscribe((response: string | undefined) => { + if (response && this.dataset != undefined) { + this.tableData = this.csvParseService.csvToArray(response, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter.toString() == "") ? "," : this.dataset.delimiter); + } }); + this.loaded = true; } - setDeleteColumnsForMissingValTreatment() { + ngAfterViewInit(): void { + + } + + setColumnTypeInitial() { + if (this.dataset != undefined) { + for (let i = 0; i < this.dataset.columnInfo.length; i++) { + this.dataset.columnInfo[i].columnType = (this.dataset.columnInfo[i].isNumber) ? ColumnType.numerical : ColumnType.categorical; + } + } + } + + setDeleteRowsForMissingValTreatment() { if (this.experiment != undefined) { this.experiment.nullValues = NullValueOptions.DeleteRows; this.experiment.nullValuesReplacers = []; @@ -67,8 +95,20 @@ export class ColumnTableComponent implements AfterViewInit { } } + columnTableChangeDetected() { + this.columnTableChanged.emit(); + } + + columnTypeChanged(columnName: string) { + if (this.experiment.outputColumn == columnName) + this.changeOutputColumn(columnName); + else + this.columnTableChangeDetected(); + } + changeInputColumns(targetMatCheckbox: MatCheckboxChange, columnName: string) { if (this.experiment != undefined) { + if (targetMatCheckbox.checked) { if (this.experiment.inputColumns.filter(x => x == columnName)[0] == undefined) { this.experiment.inputColumns.push(columnName); @@ -80,7 +120,26 @@ export class ColumnTableComponent implements AfterViewInit { //TODO: da se zatamni kolona koja je unchecked //this.experiment.encodings = this.experiment.encodings.filter(x => x.columnName != columnName); samo na kraju iz enkodinga skloni necekirane this.experiment.nullValuesReplacers = this.experiment.nullValuesReplacers.filter(x => x.column != columnName); + if (columnName == this.experiment.outputColumn) + this.experiment.outputColumn = this.experiment.inputColumns[0]; + } + this.columnTableChangeDetected(); + } + } + + changeOutputColumn(columnName: string) { + if (this.experiment != undefined && this.dataset != undefined) { + let column = this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.outputColumn)[0]; + if (column.columnType == ColumnType.numerical) { + this.experiment.type = ProblemType.Regression; } + else { + if (column.uniqueValues!.length == 2) + this.experiment.type = ProblemType.BinaryClassification; + else + this.experiment.type = ProblemType.MultiClassification; + } + this.columnTableChangeDetected(); } } @@ -91,6 +150,7 @@ export class ColumnTableComponent implements AfterViewInit { this.experiment.encodings.push(new ColumnEncoding(this.dataset?.columnInfo[i].columnName, encodingType)); //console.log(this.experiment.encodings); } + this.columnTableChangeDetected(); } } openEncodingDialog() { @@ -127,9 +187,11 @@ export class ColumnTableComponent implements AfterViewInit { option: NullValueOptions.DeleteRows, value: "" }); - this.nullValOption[i] = "Obriši redove"; + let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[i])[0]).numNulls; + this.nullValOption[i] = "Obriši redove (" + numOfRowsToDelete + ")"; } } + this.columnTableChangeDetected(); } } openMissingValuesDialog() { @@ -141,13 +203,25 @@ export class ColumnTableComponent implements AfterViewInit { this.resetMissingValuesTreatment(selectedMissingValuesOption); }); } - updateTestSet(event: MatSliderChange) { - this.testSetDistribution = event.value!; + + openSaveExperimentDialog() { + const dialogRef = this.dialog.open(SaveExperimentDialogComponent, { + width: '400px' + }); + dialogRef.afterClosed().subscribe(selectedName => { + this.experiment.name = selectedName; + //napravi odvojene dugmice za save i update -> za update nece da se otvara dijalog za ime + this.experimentService.addExperiment(this.experiment).subscribe((response) => { + console.log(response); + this.okPressed.emit(); + }); + }); } + MissValsDeleteClicked(event: Event, replacementType: NullValueOptions, index: number) { - if (this.experiment != undefined) { + if (this.experiment != undefined && this.dataset != undefined) { let columnName = (<HTMLInputElement>event.currentTarget).value; let arrayElement = this.experiment.nullValuesReplacers.filter(x => x.column == columnName)[0]; @@ -163,7 +237,9 @@ export class ColumnTableComponent implements AfterViewInit { arrayElement.value = ""; } - this.nullValOption[index] = (replacementType == NullValueOptions.DeleteColumns) ? "Obriši kolonu" : "Obriši redove"; + let numOfRowsToDelete = (this.dataset.columnInfo.filter(x => x.columnName == this.experiment!.inputColumns[index])[0]).numNulls; + this.nullValOption[index] = (replacementType == NullValueOptions.DeleteColumns) ? "Obriši kolonu" : "Obriši redove (" + numOfRowsToDelete + ")"; + this.columnTableChangeDetected(); } } @@ -185,6 +261,7 @@ export class ColumnTableComponent implements AfterViewInit { } this.nullValOption[index] = "Popuni sa: " + fillValue; + this.columnTableChangeDetected(); } } getValue(columnName: string): string { @@ -192,8 +269,8 @@ export class ColumnTableComponent implements AfterViewInit { return (<HTMLInputElement>document.getElementById(columnName)).value; return '0'; } - ok() { - this.okPressed.emit(); + saveExperiment() { + this.openSaveExperimentDialog(); } diff --git a/frontend/src/app/_elements/datatable/datatable.component.ts b/frontend/src/app/_elements/datatable/datatable.component.ts index 82374f4d..560a1c21 100644 --- a/frontend/src/app/_elements/datatable/datatable.component.ts +++ b/frontend/src/app/_elements/datatable/datatable.component.ts @@ -18,7 +18,6 @@ export class DatatableComponent implements OnInit { export class TableData { constructor( - public hasHeader = true, public hasInput = false, public loaded = false, public numRows = 0, diff --git a/frontend/src/app/_elements/folder/folder.component.css b/frontend/src/app/_elements/folder/folder.component.css index 137a9643..2340ee8a 100644 --- a/frontend/src/app/_elements/folder/folder.component.css +++ b/frontend/src/app/_elements/folder/folder.component.css @@ -129,6 +129,8 @@ .list-view { height: 100%; overflow-y: auto; + display: flex; + flex-direction: column; } .list-item { @@ -138,6 +140,7 @@ align-items: center; justify-content: space-between; border-bottom: 1px solid var(--ns-primary); + flex-shrink: 0; } .list-item:hover { @@ -149,6 +152,15 @@ display: none; } +.list-add { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + flex-grow: 1; + height: 100%; +} + .folder-inside { width: 100%; height: 40rem; @@ -157,7 +169,7 @@ .file-content { width: 100%; - height: 92%; + height: 100%; position: relative; } @@ -186,4 +198,8 @@ .file-button:hover { background-color: var(--ns-primary); +} + +.form-hidden { + display: none; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html index 36f70c97..48b59dc8 100644 --- a/frontend/src/app/_elements/folder/folder.component.html +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -1,9 +1,9 @@ <div id="folder"> <div id="tabs"> - <div id="new-file-tab" 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}"> + <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)"> - {{tabTitles[TabType.NewFile]}} + {{newFile.name}} </a> </div> <!--<div class="folder-tab p-1 rounded-top" *ngFor="let file of filteredFiles; let i = index" [style]="'z-index:' + calcZIndex(i) + ' ;'" [ngClass]="{'selected-tab' : selectedFileIndex == i, 'hover-tab' : hoveringOverFileIndex == i}"> @@ -12,6 +12,10 @@ <div class="folder-tab p-1 rounded-top" *ngFor="let tab of tabsToShow; let i = index" [style]="'z-index:' + (selectedTab == tab ? 11 : (tabsToShow.length - i)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == tab, 'hover-tab' : hoverTab == tab}"> <a class="m-1 stretched-link tab-link" (click)="selectTab(tab)" (mouseenter)="hoverOverTab(tab)" (mouseleave)="hoverOverTab(TabType.None)">{{tabTitles[tab]}}</a> </div> + + <div class="folder-tab p-1 rounded-top" *ngIf="selectedFile" [style]="'z-index:' + (selectedTab == TabType.File ? 11 : (tabsToShow.length)) + ' ;'" [ngClass]="{'selected-tab' : selectedTab == TabType.File, 'hover-tab' : hoverTab == TabType.File}"> + <a class="m-1 stretched-link tab-link" (click)="selectTab(TabType.File)" (mouseenter)="hoverOverTab(TabType.File)" (mouseleave)="hoverOverTab(TabType.None)">{{selectedFile.name}}</a> + </div> </div> <div id="selected-content" class="rounded-bottom text-offwhite"> <div id="searchbar" *ngIf="listView"> @@ -26,20 +30,20 @@ </mat-form-field> </div> <div id="search-options"> - <div id="collapseFilters" class="collapse collapse-horizontal"> + <!-- <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> @@ -49,35 +53,37 @@ </div> </div> <!--{{fileToDisplay ? fileToDisplay.name : 'No file selected.'}} {{selectedFileIndex}} {{hoveringOverFileIndex}}--> - <div [ngSwitch]="listView" class="folder-inside bg-blur"> - <div class="file-content" [ngSwitch]="type" *ngSwitchCase="false"> - <div class="file-bottom-buttons"> - <button class="btn-clear file-button" (click)="deleteFile()"> + <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)"> <mat-icon>delete</mat-icon> </button> - <button class="btn-clear file-button"> - <mat-icon>link</mat-icon> - </button> - <button class="btn-clear file-button"> + <!-- <button class="btn-clear file-button"> <mat-icon>zoom_out_map</mat-icon> - </button> + </button> --> </div> - <app-form-model [forExperiment]="forExperiment" [model]="fileToDisplay" *ngSwitchCase="FolderType.Model"></app-form-model> - <app-form-dataset *ngSwitchCase="FolderType.Dataset" ></app-form-dataset> + <app-form-model [ngClass]="{'form-hidden': type != FolderType.Model}"></app-form-model> + <app-form-dataset [ngClass]="{'form-hidden': type != FolderType.Dataset}"></app-form-dataset> </div> - <div *ngSwitchCase="true" class="list-view"> - <div *ngFor="let file of filteredFiles; let i = index" class="list-item"> + <div [ngClass]="{'form-hidden' : !listView}" class="list-view"> + <div *ngFor="let file of filteredFiles; let i = index" class="list-item force-link" (click)="selectFile(file)"> <div class="mx-2"> - <a class="force-link" (click)="selectFile(i)">{{file.name}}</a> + {{file.name}} </div> <div class="mx-2 hover-hide"> {{file.lastUpdated | date}} </div> </div> + <div class="list-add" [ngSwitch]="type"> + <button mat-raised-button *ngSwitchCase="FolderType.Dataset" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javni ' : ' '}}izvor podataka</button> + <button mat-raised-button *ngSwitchCase="FolderType.Model" (click)="selectNewFile()">Dodaj {{privacy == Privacy.Public ? 'javnu ' : ' '}}konfiguraciju neuronske mreže</button> + <button mat-raised-button *ngSwitchCase="FolderType.Experiment" routerLink="/experiment">Dodaj eksperiment</button> + </div> </div> </div> </div> - <div id="footer" [ngSwitch]="newFileSelected"> + <div id="footer" [ngSwitch]="newFileSelected" *ngIf="!listView"> <button mat-button (click)="saveNewFile()" class="bottom-button text-offwhite rounded-bottom" *ngSwitchCase="true"> <div class="f-row"> <div>Sačuvaj</div> diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts index 06b4d893..20ca1121 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -1,33 +1,35 @@ -import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import Dataset from 'src/app/_data/Dataset'; import { FolderFile, FolderType } from 'src/app/_data/FolderFile'; import Model from 'src/app/_data/Model'; import { DatasetsService } from 'src/app/_services/datasets.service'; -import shared from 'src/app/Shared'; +import Shared from 'src/app/Shared'; import { ModelsService } from 'src/app/_services/models.service'; import { FormDatasetComponent } from '../form-dataset/form-dataset.component'; import Experiment from 'src/app/_data/Experiment'; import { ExperimentsService } from 'src/app/_services/experiments.service'; import { PredictorsService } from 'src/app/_services/predictors.service'; +import { SignalRService } from 'src/app/_services/signal-r.service'; +import { FormModelComponent } from '../form-model/form-model.component'; @Component({ selector: 'app-folder', templateUrl: './folder.component.html', styleUrls: ['./folder.component.css'] }) -export class FolderComponent implements OnInit { +export class FolderComponent implements AfterViewInit { - @ViewChild(FormDatasetComponent) formDataset?: FormDatasetComponent; + @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent; + @ViewChild(FormModelComponent) formModel!: FormModelComponent; @Input() folderName: string = 'Moji podaci'; - @Input() files!: FolderFile[] - newFile!: Dataset | Model; + newFile?: Dataset | Model; @Input() type: FolderType = FolderType.Dataset; - - @Input() forExperiment?: Experiment; + @Input() forExperiment!: Experiment; + @Input() startingTab!: TabType; newFileSelected: boolean = true; @@ -42,34 +44,32 @@ export class FolderComponent implements OnInit { searchTerm: string = ''; - constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService) { - //PLACEHOLDER - this.forExperiment = new Experiment(); - this.forExperiment.inputColumns = ['kolona1', 'kol2', '???', 'test']; + constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService) { + this.tabsToShow.forEach(tab => this.folders[tab] = []); + } - this.folders[TabType.File] = []; - this.folders[TabType.NewFile] = []; + ngAfterViewInit(): void { + this.refreshFiles(null); - this.refreshFiles(); + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => { + this.refreshFiles(dId); - - } - - ngOnInit(): void { - if (this.files.length > 0) - this.selectFile(0); - else { - this.selectNewFile(); + }); + } else { + console.warn("Dataset-Load: No connection!"); } } - displayFile(){ - if(this.type == FolderType.Dataset) - this.formDataset!.dataset = <Dataset>this.fileToDisplay; + displayFile() { + if (this.type == FolderType.Dataset) + this.formDataset.dataset = <Dataset>this.fileToDisplay; + else if (this.type == FolderType.Model) + this.formModel.newModel = <Model>this.fileToDisplay; } hoverOverFile(i: number) { - this.hoveringOverFileIndex = i; + /*this.hoveringOverFileIndex = i; if (i != -1) { this.fileToDisplay = this.files[i]; } else { @@ -79,7 +79,7 @@ export class FolderComponent implements OnInit { this.fileToDisplay = this.files[this.selectedFileIndex]; } } - this.displayFile(); + this.displayFile();*/ } selectNewFile() { @@ -87,20 +87,19 @@ export class FolderComponent implements OnInit { this.createNewFile(); } this.fileToDisplay = this.newFile; - this.selectedFile = this.newFile; this.newFileSelected = true; this.listView = false; - this.selectedFileChanged.emit(this.newFile); this.displayFile(); } - selectFile(index: number) { - this.selectedFile = this.filteredFiles[index]; - this.fileToDisplay = this.filteredFiles[index]; + selectFile(file?: FolderFile) { + this.selectedFile = file; + this.fileToDisplay = file; this.newFileSelected = false; this.listView = false; this.selectedFileChanged.emit(this.selectedFile); this.displayFile(); + this.selectTab(TabType.File); } createNewFile() { @@ -115,9 +114,22 @@ export class FolderComponent implements OnInit { this.okPressed.emit(); } - refreshFiles(){ + _initialized: boolean = false; + + refreshFiles(selectedDatasetId: string | null) { + this.tabsToShow.forEach(tab => { + this.folders[tab] = []; + }) + this.datasetsService.getMyDatasets().subscribe((datasets) => { this.folders[TabType.MyDatasets] = datasets; + if (selectedDatasetId) { + this.selectFile(datasets.filter(x => x._id == selectedDatasetId)[0]); + } + }); + + this.experimentsService.getMyExperiments().subscribe((experiments) => { + this.folders[TabType.MyExperiments] = experiments; }); this.datasetsService.getPublicDatasets().subscribe((datasets) => { @@ -125,6 +137,7 @@ export class FolderComponent implements OnInit { }); this.modelsService.getMyModels().subscribe((models) => { + console.log(models); this.folders[TabType.MyModels] = models; }); @@ -137,20 +150,40 @@ export class FolderComponent implements OnInit { this.folders[TabType.MyExperiments] = experiments; }); - this.files = []; - - this.filteredFiles.length = 0; - this.filteredFiles.push(...this.files); + if (!this._initialized) { + this.files = this.folders[this.startingTab]; + this.filteredFiles = []; + this.selectTab(this.startingTab); + this._initialized = true; + } this.searchTermsChanged(); - } saveNewFile() { - if(this.type == FolderType.Dataset) - this.formDataset!.uploadDataset(); + switch (this.type) { + case FolderType.Dataset: + this.formDataset!.uploadDataset((dataset: Dataset) => { + Shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira."); + this.refreshFiles(dataset._id); + }, + () => { + Shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); + }); + break; + case FolderType.Model: + this.modelsService.addModel(this.formModel.newModel).subscribe(model => { + this.formModel.newModel = model; + Shared.openDialog("Obaveštenje", "Uspešno ste dodali novu konfiguraciju neuronske mreže u kolekciju."); + this.refreshFiles(null); // todo select model + }, (err) => { + Shared.openDialog("Neuspeo pokušaj!", "Konfiguracija neuronske mreže sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeću konfiguraciju."); + }); + break; + } } + /*calcZIndex(i: number) { let zIndex = (this.files.length - i - 1) if (this.selectedFileIndex == i) @@ -159,7 +192,7 @@ export class FolderComponent implements OnInit { zIndex = this.files.length + 3; return zIndex; } - + newFileZIndex() { return (this.files.length + 1); }*/ @@ -172,25 +205,47 @@ export class FolderComponent implements OnInit { filteredFiles: FolderFile[] = []; searchTermsChanged() { + if (!this.files) return; this.filteredFiles.length = 0; this.filteredFiles.push(...this.files.filter((file) => file.name.toLowerCase().includes(this.searchTerm.toLowerCase()))); - if (this.selectedFile) { + /*if (this.selectedFile) { if (!this.filteredFiles.includes(this.selectedFile)) { - this.selectFile(-1); + if (this.hoverTab === TabType.None && this.getFolderType(this.selectedTab) === this.type) { + this.selectFile(undefined); + console.log(this.getFolderType(this.selectedTab), this.type); + } } else { - this.selectedFileIndex = this.filteredFiles.indexOf(this.selectedFile); + //this.selectedFileIndex = this.filteredFiles.indexOf(this.selectedFile); } - } + }*/ } - listView: boolean = false; + listView: boolean = true; toggleListView() { this.listView = !this.listView; } - deleteFile() { + deleteFile(file: FolderFile) { console.log('delete'); + switch (this.type) { + case FolderType.Dataset: + this.datasetsService.deleteDataset(<Dataset>file).subscribe((response) => { + console.log(response); + }); + break; + case FolderType.Model: + this.modelsService.deleteModel(<Model>file).subscribe((response) => { + console.log(response); + }); + break; + case FolderType.Experiment: + // this.experimentsService.deleteExperiment(<Model>file).subscribe((response) => { + // console.log(response); + // }); + //todo delete za predictor + break; + } } folders: { [tab: number]: FolderFile[] } = {}; @@ -206,51 +261,87 @@ export class FolderComponent implements OnInit { }; FolderType = FolderType; - + Privacy = Privacy; TabType = TabType; + privacy: Privacy = Privacy.Private; + @Input() tabsToShow: TabType[] = [ TabType.MyDatasets, TabType.PublicDatasets, TabType.MyModels, TabType.PublicModels, - TabType.MyExperiments, - TabType.File + TabType.MyExperiments ] @Input() selectedTab: TabType = TabType.NewFile; hoverTab: TabType = TabType.None; selectTab(tab: TabType) { - this.checkListView(tab); - this.selectedTab = tab; - this.files = this.folders[tab]; + setTimeout(() => { + if (tab == TabType.NewFile) { + this.selectNewFile(); + } - this.searchTermsChanged(); + this.listView = this.getListView(tab); + this.type = this.getFolderType(tab); + this.privacy = this.getPrivacy(tab); + this.selectedTab = tab; + this.files = this.folders[tab]; + + if (tab !== TabType.File && tab !== TabType.NewFile) + this.searchTermsChanged(); + }); } - checkListView(tab: TabType) { + getListView(tab: TabType) { switch (tab) { case TabType.File: case TabType.NewFile: case TabType.None: - this.listView = false; - break; + return false; case TabType.MyExperiments: case TabType.MyDatasets: case TabType.MyModels: case TabType.PublicDatasets: case TabType.PublicModels: - this.listView = true; - break; + return true; + default: + return false; + } + } + + getFolderType(tab: TabType) { + switch (tab) { + case TabType.MyExperiments: + return FolderType.Experiment; + case TabType.MyDatasets: + case TabType.PublicDatasets: + return FolderType.Dataset; + case TabType.MyModels: + case TabType.PublicModels: + return FolderType.Model; + default: + return this.type; + } + } + + getPrivacy(tab: TabType) { + switch (tab) { + case TabType.PublicDatasets: + case TabType.PublicModels: + return Privacy.Public; + default: + return Privacy.Private; } } hoverOverTab(tab: TabType) { - this.checkListView(tab); + this.listView = this.getListView(tab); + this.privacy = this.getPrivacy(tab); this.hoverTab = tab; if (tab == TabType.None) { - this.checkListView(this.selectedTab); + this.listView = this.getListView(this.selectedTab); this.files = this.folders[this.selectedTab]; } else { this.files = this.folders[tab]; 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 da31cfcb..7c7eb0d3 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.css +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.css @@ -4,26 +4,23 @@ position: relative; } +.bottomBar { + width: 50%; + margin: 1rem; + align-items: flex-start; +} + +.fileButton{ + margin-top: 10px; +} + .file-container { border: 4px solid transparent; position: relative; margin-left: 3%; - margin-top: 3rem; width: 94%; - min-height: 300px; - height: 75%; -} - -.fileButton { - position: absolute; - margin-top: -3rem; - display: flex; - flex-direction: row; - align-items: center; -} - -.fileButton label { - margin-left: 10px; + min-height: 400px; + height: 95%; } .dottedClass { @@ -31,6 +28,13 @@ border-radius: 25px; } +.icon-display { + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%) scale(4); +} + .hidden { visibility: hidden; } @@ -42,28 +46,7 @@ opacity: 0; } -.file input { - border-radius: 4px; - margin-top: -15px; - width: 100%; - height: 100%; -} - -.icon-display { - position: absolute; - top: 45%; - left: 50%; - transform: translate(-50%, -50%) scale(4); -} - -.bottomBar { - width: 50%; - margin: 1rem; - align-items: flex-start; -} - -#bottomButton { - background-color: var(--ns-bg-dark-100); - width: 10%; - height: 65%; +.file-container input{ + border-radius: 5px; + left: 0%; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html index 2176b130..5c2b29ba 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html @@ -1,74 +1,58 @@ -<div class="folderBox"> - - <div class="file-container" [ngClass]="{'dottedClass': !tableData.hasInput}"> - - <i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i> - - <div class="fileButton"> - <button type="button" mat-raised-button (click)="fileInput.click()">Choose File</button> - <label>{{filename}}</label> - </div> - - <input class="file" id="file-upload" (change)="changeListener($event)" #fileInput type="file" accept=".csv"> - - - <div class="mt-5 datatable"> - <app-datatable [tableData]="tableData"></app-datatable> - </div> - - - </div> - +<div class="folderBox" *ngIf="dataset"> + + <div class="row" style="margin-right: 0;"> + <div class="bottomBar"> + <div class="row"> + <div class="col-sm mb-3"> + <div class="fileButton"> + <button type="button" mat-raised-button (click)="fileInput.click()">Choose File</button> + <label>{{filename}}</label> + </div> + </div> - - + <div class="col-sm"> + <div role="group"> + <div class="row"> + <mat-form-field class="example-full-width" appearance="fill"> + <mat-label>Naziv</mat-label> + <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name"> - <div class="bottomBar"> - <div class="row"> - <div class="col-sm"> - <div role="group"> - <div class="row"> - <mat-form-field class="example-full-width" appearance="fill"> - <mat-label>Naziv</mat-label> - <input type="text" matInput value="{{dataset?.name}}"> - <!--[formControl]="nameFormControl"--> - <mat-error *ngIf="nameFormControl.hasError('required')"> - Naziv je <strong>obavezan</strong> - </mat-error> - </mat-form-field> + <mat-error *ngIf="nameFormControl.hasError('required')"> + Naziv je <strong>obavezan</strong> + </mat-error> + </mat-form-field> + </div> </div> </div> + <div class="col-sm"> + <mat-form-field appearance="fill"> + <mat-label>Delimiter</mat-label> + <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (change)="update()" value=","> + <mat-option *ngFor="let option of delimiterOptions" [value]="option"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + </div> </div> - <div class="col-sm mb-3"> + </div> + </div> - <!--<input id="fileInput" class="form-control btn-lg" type="file" class="upload" (change)="changeListener($event)" accept=".csv"> - --> - </div> - <div class="col-sm"> + <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> - <mat-form-field appearance="fill"> - <mat-label>Delimiter</mat-label> - <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (change)="update()" value=","> - <mat-option *ngFor="let option of delimiterOptions" [value]="option"> - {{ option }} - </mat-option> - </mat-select> - </mat-form-field> + <input class="file" id="file-upload" (change)="changeListener($event)" #fileInput type="file" accept=".csv"> + + <div class="mt-5 datatable"> + <app-datatable [tableData]="tableData"></app-datatable> </div> - </div> - </div> - <div class="btn-group" role="group" aria-label="Button group with nested dropdown"> + </div> </div> - - <!-- - <div class="d-flex flex-row align-items-center justify-content-center w-100 my-2"> - <button (click)="uploadDataset()" class="btn btn-lg col-4" style="background-color:#003459; color:white;">Dodaj izvor podataka</button> - </div> ---> </div>
\ No newline at end of file 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 63376524..62afaa47 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -5,7 +5,7 @@ import { ModelsService } from 'src/app/_services/models.service'; import shared from 'src/app/Shared'; import { DatatableComponent, TableData } from '../datatable/datatable.component'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; -import {FormControl, Validators} from '@angular/forms'; +import { FormControl, Validators } from '@angular/forms'; @Component({ selector: 'app-form-dataset', @@ -18,7 +18,7 @@ export class FormDatasetComponent { nameFormControl = new FormControl('', [Validators.required, Validators.email]); - delimiterOptions: Array<string> = [",", ";", "|", "razmak", "novi red"]; //podrazumevano "," + delimiterOptions: Array<string> = [",", ";", "|", "razmak", "novi red"]; //podrazumevano "," csvRecords: any[] = []; files: File[] = []; @@ -29,7 +29,7 @@ export class FormDatasetComponent { tableData: TableData = new TableData(); - @ViewChild('fileInput') fileInput! : ElementRef + @ViewChild('fileInput') fileInput!: ElementRef filename: String; @@ -65,16 +65,13 @@ export class FormDatasetComponent { if (typeof fileReader.result === 'string') { const result = this.csv.csvToArray(fileReader.result, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter) - if (this.dataset.hasHeader) - this.csvRecords = result.splice(0, 11); - else - this.csvRecords = result.splice(0, 10); + + this.csvRecords = result.splice(0, 11); this.colsNumber = result[0].length; this.rowsNumber = result.length; - this.tableData.data = this.csvRecords - this.tableData.hasHeader = this.dataset.hasHeader; + this.tableData.data = this.csvRecords; this.tableData.loaded = true; this.tableData.numCols = this.colsNumber; this.tableData.numRows = this.rowsNumber; @@ -85,32 +82,37 @@ export class FormDatasetComponent { this.dataset.name = this.filename.slice(0, this.filename.length - 4); } + /*exportAsXLSX():void { + this.excelService.exportAsExcelFile(this.data, 'sample'); + }*/ + checkAccessible() { if (this.dataset.isPublic) this.dataset.accessibleByLink = true; } - uploadDataset() { + uploadDataset(onSuccess: Function = (dataset: Dataset) => { }, onError: Function = () => { }) { if (this.files[0] == undefined) { shared.openDialog("Greška", "Niste izabrali fajl za učitavanje."); return; } - this.modelsService.uploadData(this.files[0]).subscribe((file) => { + return this.modelsService.uploadData(this.files[0]).subscribe((file) => { //console.log('ADD MODEL: STEP 2 - ADD DATASET WITH FILE ID ' + file._id); + this.dataset._id = ""; this.dataset.fileId = file._id; this.dataset.uploaderId = shared.userId; this.datasetsService.addDataset(this.dataset).subscribe((dataset) => { - shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira."); + onSuccess(); }, (error) => { - shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); + onError(); }); //kraj addDataset subscribe }, (error) => { - + onError(); }); //kraj uploadData subscribe } - + } diff --git a/frontend/src/app/_elements/form-model/form-model.component.css b/frontend/src/app/_elements/form-model/form-model.component.css index 8c279523..11b6ef5e 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.css +++ b/frontend/src/app/_elements/form-model/form-model.component.css @@ -84,4 +84,21 @@ hr { .m-2 { max-height: 20 rem; +} + +mat-slider { + width: 50%; +} + +.slider { + background-color: transparent; +} + +.center-center { + text-align: center; + margin-right: 10px; + padding-right: 10px; + padding-bottom: 15px; + font-size: 20px !important; + font-weight: 600; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html index 76601465..c0318012 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -1,202 +1,226 @@ -<div id="container"> - <div class="ns-row"> +<div *ngIf="newModel"> + <div id="container"> + <div class="ns-row"> + + <div class="ns-col"> + <mat-form-field class="example-full-width" appearance="fill" class="mat-fix"> + <mat-label>Naziv</mat-label> + <input type="text" matInput [(ngModel)]="newModel.name"> + </mat-form-field> + </div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Tip problema</mat-label> + <mat-select [(ngModel)]="newModel.type"> + <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-1"></div> + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Optimizacija</mat-label> + <mat-select [(ngModel)]="newModel.optimizer"> + <mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + + </mat-form-field> + </div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Funkcija troška</mat-label> + <mat-select [(ngModel)]="newModel.lossFunction"> + <mat-option *ngFor="let option of Object.keys(LossFunction); let optionName of Object.values(LossFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-2"></div> + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Funkcija aktivacije izlaznog sloja</mat-label> + <mat-select name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Stopa učenja</mat-label> + <mat-select [(ngModel)]="newModel.learningRate"> + <mat-option *ngFor="let option of Object.keys(LearningRate); let optionName of Object.values(LearningRate)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + + <div class="break-1"></div> + + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Broj epoha</mat-label> + <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000"> + </mat-form-field> + </div> + <div class="ns-col"> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Broj uzoraka po iteraciji</mat-label> + + <mat-select matNativeControl [(value)]="newModel.batchSize"> + <mat-option *ngFor="let option of Object.keys(BatchSize); let optionName of Object.values(BatchSize)" [value]="option">{{option}}</mat-option> + </mat-select> + </mat-form-field> + </div> - <div class="ns-col"> - <mat-form-field class="example-full-width" appearance="fill" class="mat-fix"> - <mat-label>Naziv</mat-label> - <input type="text" matInput [(ngModel)]="newModel.name"> - </mat-form-field> </div> - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Tip problema</mat-label> - <mat-select [(ngModel)]="newModel.type"> - <mat-option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> + </div> + + <!-- GRAF --> + + <div class="m-2"> + <div class="row"> + <div class="col-sm-3 rounded" style="border:1px solid var(--ns-primary);margin-top: 10px;"> + <div class="row slider rounded mb-3" style="margin-left: 10px;"> + + <div class="text-center pt-3 pb-0 mb-0"><b>{{testSetDistribution}}%</b> : <b>{{100-testSetDistribution}}%</b></div> + <div class="text-center pt-0 mt-0">Trening + <mat-slider min="10" max="90" step="10" [(ngModel)]="testSetDistribution" (input)="updateTestSet($event)"></mat-slider> + Test</div> + + </div> + <div class="row slider rounded text-offwhite justify-content-center align-items-center" style="margin-left: 10px;"> + <mat-checkbox class="pt-4 mb-3" color="accent">Nasumični redosled podataka</mat-checkbox> + </div> + + + </div> + + <div class="col-sm-9"> + <app-graph [model]="newModel"></app-graph> + </div> </div> + </div> - <div class="break-1"></div> + <!-- SVI LAYERI --> - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Optimizacija</mat-label> - <mat-select [(ngModel)]="newModel.optimizer"> - <mat-option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> + <div class="ns-row"> + + <div class="ns-col" id="layers-control"> + <div>Broj Skrivenih Slojeva</div> + <button class="btn-clear btn-icon bubble" (click)="addLayer()"> + <mat-icon>add</mat-icon> + </button> + <div>{{newModel.hiddenLayers}}</div> + <button class="btn-clear btn-icon bubble" (click)="removeLayer()"> + <mat-icon>remove</mat-icon> + </button> - </mat-form-field> </div> + <div class="break-1"></div> <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Funkcija troška</mat-label> - <mat-select [(ngModel)]="newModel.lossFunction"> - <mat-option *ngFor="let option of Object.keys(LossFunction); let optionName of Object.values(LossFunction)" [value]="option"> + <mat-label>Aktivaciona funkcija svih slojeva</mat-label> + + <mat-select [(ngModel)]="selectedActivation" (selectionChange)="changeAllActivation()"> + <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> {{ optionName }} </mat-option> </mat-select> </mat-form-field> </div> - <div class="break-2"></div> - <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Funkcija aktivacije izlaznog sloja</mat-label> - <mat-select name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> - <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> + <mat-label>Broj neurona svih slojeva</mat-label> + <input matInput type="number" min="1" max="18" [(ngModel)]="selectedNumberOfNeurons" (change)="changeAllNumberOfNeurons()"> </mat-form-field> </div> + <div class="break-2"></div> <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Stopa učenja</mat-label> - <mat-select [(ngModel)]="newModel.learningRate"> - <mat-option *ngFor="let option of Object.keys(LearningRate); let optionName of Object.values(LearningRate)" [value]="option"> + <mat-label>Regularizacija svih slojeva</mat-label> + <mat-select [(ngModel)]="selectedRegularisation" (selectionChange)="changeAllRegularisation()"> + <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> {{ optionName }} </mat-option> </mat-select> </mat-form-field> </div> - <div class="break-1"></div> + <div class="ns-col"> <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Broj epoha</mat-label> - <input type="number" matInput [(ngModel)]="newModel.epochs" min="1" max="1000"> - </mat-form-field> - </div> - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Broj uzoraka po iteraciji</mat-label> - - <mat-select matNativeControl required [(value)]="newModel.batchSize"> - <mat-option *ngFor="let option of Object.keys(BatchSize); let optionName of Object.values(BatchSize)" [value]="option">{{option}}</mat-option> + <mat-label>Stopa regularizacije svih slojeva</mat-label> + <mat-select [(ngModel)]="selectedRegularisationRate" (selectionChange)="changeAllRegularisationRate()"> + <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> + {{ optionName }} + </mat-option> </mat-select> </mat-form-field> </div> - </div> -</div> - - -<!--kraj unosa parametara--> -<hr> -<div class="m-2"> - <app-graph [model]="newModel" [inputColumns]="forExperiment?.inputColumns"></app-graph> -</div> -<div class="ns-row"> - - <div class="ns-col" id="layers-control"> - <div>Broj Skrivenih Slojeva</div> - <button class="btn-clear btn-icon bubble" (click)="addLayer()"> - <mat-icon>add</mat-icon> - </button> - <div>{{newModel.hiddenLayers}}</div> - <button class="btn-clear btn-icon bubble" (click)="removeLayer()"> - <mat-icon>remove</mat-icon> - </button> - - </div> - <div class="break-1"></div> - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Aktivaciona funkcija svih slojeva</mat-label> - - <mat-select [(ngModel)]="selectedActivation" (selectionChange)="changeAllActivation()"> - <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - </div> - - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Broj neurona svih slojeva</mat-label> - <input matInput type="number" min="1" max="18" [(ngModel)]="selectedNumberOfNeurons" (change)="changeAllNumberOfNeurons()"> - </mat-form-field> - </div> - <div class="break-2"></div> - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Regularizacija svih slojeva</mat-label> - <mat-select [(ngModel)]="selectedRegularisation" (selectionChange)="changeAllRegularisation()"> - <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - </div> - - <div class="ns-col"> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Stopa regularizacije svih slojeva</mat-label> - <mat-select [(ngModel)]="selectedRegularisationRate" (selectionChange)="changeAllRegularisationRate()"> - <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - </div> - - -</div> - -<!--kraj selectall**********************************************************************************--> -<div id="layers"> - - <div class="layer" *ngFor="let item of newModel.layers; let i=index"> - - - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Aktivacija</mat-label> - <button matPrefix class="btn-clear center-center text-offwhite"> - <div> - #{{i+1}} - </div> - </button> - <mat-select [(ngModel)]="newModel.layers[i].activationFunction"> - <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - - <div class="d-flex flex-row align-items-center justify-content-center tm"> - <div class="col-6" style="font-size: 13px;">Broj čvorova</div> - <button class="btn-clear btn-icon bubble" (click)="addNeuron(i)"> + <!-- LAYERI --> + + <div id="layers"> + <div class="layer" *ngFor="let item of newModel.layers; let i=index"> + + + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Aktivacija</mat-label> + <button matPrefix class="btn-clear center-center text-offwhite"> + <div> + #{{i+1}} + </div> + </button> + <mat-select [(ngModel)]="newModel.layers[i].activationFunction"> + <mat-option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + + <div class="d-flex flex-row align-items-center justify-content-center tm"> + <div class="col-6" style="font-size: 13px;">Broj čvorova</div> + <button class="btn-clear btn-icon bubble" (click)="addNeuron(i)"> <mat-icon>add</mat-icon> </button> - <div class="col-2 text-center">{{newModel.layers[i].neurons}}</div> - <button class="btn-clear btn-icon bubble" (click)="removeNeuron(i)"> + <div class="col-2 text-center">{{newModel.layers[i].neurons}}</div> + <button class="btn-clear btn-icon bubble" (click)="removeNeuron(i)"> <mat-icon>remove</mat-icon> </button> - </div> + </div> - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Regularizacija</mat-label> - <mat-select [(ngModel)]="newModel.layers[i].regularisation"> - <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> - - <mat-form-field appearance="fill" class="mat-fix"> - <mat-label>Stopa regularizacije</mat-label> - <mat-select [(ngModel)]="newModel.layers[i].regularisationRate"> - <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> - {{ optionName }} - </mat-option> - </mat-select> - </mat-form-field> + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Regularizacija</mat-label> + <mat-select [(ngModel)]="newModel.layers[i].regularisation"> + <mat-option *ngFor="let option of Object.keys(Regularisation); let optionName of Object.values(Regularisation)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + + <mat-form-field appearance="fill" class="mat-fix"> + <mat-label>Stopa regularizacije</mat-label> + <mat-select [(ngModel)]="newModel.layers[i].regularisationRate"> + <mat-option *ngFor="let option of Object.keys(RegularisationRate); let optionName of Object.values(RegularisationRate)" [value]="option"> + {{ optionName }} + </mat-option> + </mat-select> + </mat-form-field> + </div> + </div> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts index 2c78cd56..71b374b0 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -4,7 +4,7 @@ import Shared from 'src/app/Shared'; import Experiment from 'src/app/_data/Experiment'; import Model, { Layer, ActivationFunction, LossFunction, LearningRate, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType, Regularisation, RegularisationRate, BatchSize } from 'src/app/_data/Model'; import { GraphComponent } from '../graph/graph.component'; - +import { MatSliderChange } from '@angular/material/slider'; @Component({ selector: 'app-form-model', @@ -13,13 +13,12 @@ import { GraphComponent } from '../graph/graph.component'; }) export class FormModelComponent implements AfterViewInit { @ViewChild(GraphComponent) graph!: GraphComponent; - @Input() forExperiment?: Experiment; + @Input() forExperiment!: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); - + testSetDistribution: number = 70; constructor() { } - ngAfterViewInit(): void { - } + ngAfterViewInit(): void { } selectFormControl = new FormControl('', Validators.required); nameFormControl = new FormControl('', [Validators.required, Validators.email]); @@ -34,8 +33,7 @@ export class FormModelComponent implements AfterViewInit { selectRegularisationFormControl = new FormControl('', Validators.required); selectRRateFormControl = new FormControl('', Validators.required); - newModel: Model = new Model(); - myModels?: Model[]; + newModel!: Model; selectedModel?: Model; @@ -57,7 +55,9 @@ export class FormModelComponent implements AfterViewInit { selectedMetrics = []; lossFunction: any = LossFunction; - showMyModels: boolean = true; + loadModel(model: Model) { + this.newModel = model; + } updateGraph() { //console.log(this.newModel.layers); @@ -121,7 +121,6 @@ export class FormModelComponent implements AfterViewInit { } } changeAllRegularisationRate() { - for (let i = 0; i < this.newModel.layers.length; i++) { this.newModel.layers[i].regularisationRate = this.selectedRegularisationRate; } @@ -133,6 +132,7 @@ export class FormModelComponent implements AfterViewInit { } } - - + updateTestSet(event: MatSliderChange) { + this.testSetDistribution = event.value!; + } } diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css index 361e7249..456a8df1 100644 --- a/frontend/src/app/_elements/graph/graph.component.css +++ b/frontend/src/app/_elements/graph/graph.component.css @@ -1,6 +1,5 @@ .node-text { position: absolute; - color: transparent; width: 100px; height: 40px; text-align: center; @@ -12,6 +11,10 @@ transform: translate(-50%, -50%); } -.node-text:hover { +.node-text:not(.inputs) { + color: transparent; +} + +.node-text:not(.inputs):hover { color: var(--offwhite); }
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html index 19e5c14a..3c01bc5e 100644 --- a/frontend/src/app/_elements/graph/graph.component.html +++ b/frontend/src/app/_elements/graph/graph.component.html @@ -1,6 +1,6 @@ <div #graphWrapper class="w-100 position-relative" style="height: 14rem;"> <ng-container *ngFor="let layer of layers; let i = index"> - <div class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 99.4" [style.top.%]="node.y * 100"> + <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[j] : 'nepoznato') : (i > 0 && i < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? 'out' : '')) }} </div> </ng-container> diff --git a/frontend/src/app/_elements/graph/graph.component.ts b/frontend/src/app/_elements/graph/graph.component.ts index 31814c2c..c7f8d964 100644 --- a/frontend/src/app/_elements/graph/graph.component.ts +++ b/frontend/src/app/_elements/graph/graph.component.ts @@ -28,7 +28,7 @@ export class GraphComponent implements AfterViewInit { @Input() outputNodeColor: string = '#dfd7d7'; private ctx!: CanvasRenderingContext2D; - @Input() inputColumns?: string[] = []; + @Input() inputColumns?: string[] = ['Nije odabran eksperiment']; constructor() { } @@ -43,6 +43,7 @@ export class GraphComponent implements AfterViewInit { window.addEventListener('resize', () => { this.resize() }); this.update(); this.resize(); + console.log(this.layers); } layers: Node[][] = []; diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.css b/frontend/src/app/_elements/metric-view/metric-view.component.css index e69de29b..f91c1ccc 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.css +++ b/frontend/src/app/_elements/metric-view/metric-view.component.css @@ -0,0 +1,10 @@ +#container{ + width: 100%; + height: 90%; + border-radius: 5px; + background-color:var(--ns-primary-25); + border:1px solid var(--ns-accent); +} +#line{ + background-color:#dfd7d7f0 ; +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.html b/frontend/src/app/_elements/metric-view/metric-view.component.html index 3a6cce8d..d72bc92b 100644 --- a/frontend/src/app/_elements/metric-view/metric-view.component.html +++ b/frontend/src/app/_elements/metric-view/metric-view.component.html @@ -1,3 +1,8 @@ -<div> - +<div id="container" class="d-flex justify-content-center flex-row w-100"> + <div id="line" style="width: 100%;height: 100%;background-color:var(--ns-bg-dark-100);"> + <app-line-chart></app-line-chart> + </div> + <div style="background-color: var(--ns-bg-dark-100);width: 50%;height: 50%;"> + <app-scatterchart></app-scatterchart> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.css diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html new file mode 100644 index 00000000..a0b5d771 --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.html @@ -0,0 +1,12 @@ +<h1 mat-dialog-title>Čuvanje eksperimenta</h1> +<div mat-dialog-content> + <p>Unesite naziv eksperimenta:</p> + <mat-form-field> + <input type="text" matInput [(ngModel)]="selectedName"> + </mat-form-field> + <p>Da li ste sigurni u izbor?</p> +</div> +<div mat-dialog-actions> + <button mat-button [mat-dialog-close]="selectedName" cdkFocusInitial>Da</button> + <button mat-button (click)="onNoClick()">Odustani</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts new file mode 100644 index 00000000..5fd6cb71 --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaveExperimentDialogComponent } from './save-experiment-dialog.component'; + +describe('SaveExperimentDialogComponent', () => { + let component: SaveExperimentDialogComponent; + let fixture: ComponentFixture<SaveExperimentDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaveExperimentDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SaveExperimentDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts new file mode 100644 index 00000000..ca01f57e --- /dev/null +++ b/frontend/src/app/_modals/save-experiment-dialog/save-experiment-dialog.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'app-save-experiment-dialog', + templateUrl: './save-experiment-dialog.component.html', + styleUrls: ['./save-experiment-dialog.component.css'] +}) +export class SaveExperimentDialogComponent implements OnInit { + + selectedName: string = ''; + + constructor(public dialogRef: MatDialogRef<SaveExperimentDialogComponent>) { } + + ngOnInit(): void { + } + + onNoClick() { + this.dialogRef.close(); + } +} diff --git a/frontend/src/app/_pages/experiment/experiment.component.css b/frontend/src/app/_pages/experiment/experiment.component.css index aca0562a..36c35484 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.css +++ b/frontend/src/app/_pages/experiment/experiment.component.css @@ -49,7 +49,7 @@ mat-stepper { } .step-content-inside { - width: 90%; - height: 90%; + width: 98%; + height: 98%; overflow-y: auto; }
\ No newline at end of file diff --git a/frontend/src/app/_pages/experiment/experiment.component.html b/frontend/src/app/_pages/experiment/experiment.component.html index 08d709b2..baae864e 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.html +++ b/frontend/src/app/_pages/experiment/experiment.component.html @@ -1,4 +1,4 @@ -<div class="container-fluid p-0 text-offwhite holder" style="height: calc(100vh - 64px);"> +<div class="container-fluid p-0 text-offwhite holder" style="height: calc(100vh - 64px); text-align: center;"> <div class="d-flex flex-colum align-items-center sidenav"> <mat-stepper orientation="vertical" (selectionChange)="changePage($event)"> <mat-step> @@ -18,22 +18,31 @@ <ng-template matStepLabel><span class="label">Treniranje</span></ng-template> <p>Odaberite parametre i trenirajte model</p> </mat-step> + <mat-step> + <ng-template matStepLabel><span class="label">Pregled rezultata treniranja</span></ng-template> + <p>Pregledajte tok treniranja i grafički prikaz rezultata</p> + </mat-step> </mat-stepper> </div> <div #stepsContainer class="steps-container"> <div #steps id="step_1" class="step-content"> <div class="step-content-inside"> - <app-folder [type]="FolderType.Dataset" [tabsToShow]="[TabType.MyDatasets, TabType.PublicDatasets, TabType.File]" (okPressed)="goToPage(1)"></app-folder> + <app-folder #folderDataset [type]="FolderType.Dataset" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyDatasets, TabType.PublicDatasets]" (okPressed)="goToPage(1)" (selectedFileChanged)="setDataset($event)"></app-folder> </div> </div> <div #steps id="step_2" class="step-content"> <div class="step-content-inside"> - <app-column-table (okPressed)="goToPage(2)"></app-column-table> + <app-column-table (okPressed)="goToPage(2)" (columnTableChanged)="columnTableChangedEvent()" [experiment]="experiment" [dataset]="dataset"></app-column-table> </div> </div> <div #steps id="step_3" class="step-content"> <div class="step-content-inside"> - <app-folder [type]="FolderType.Model" [tabsToShow]="[TabType.MyModels, TabType.PublicModels, TabType.File]" (okPressed)="goToPage(0)"></app-folder> + <app-folder #folderModel [type]="FolderType.Model" [forExperiment]="experiment" [startingTab]="TabType.NewFile" [tabsToShow]="[TabType.MyModels]" (okPressed)="goToPage(3)"></app-folder> + </div> + </div> + <div #steps id="step_4" class="step-content"> + <div class="step-content-inside"> + <app-metric-view></app-metric-view> </div> </div> </div> diff --git a/frontend/src/app/_pages/experiment/experiment.component.ts b/frontend/src/app/_pages/experiment/experiment.component.ts index 70f941b6..28552664 100644 --- a/frontend/src/app/_pages/experiment/experiment.component.ts +++ b/frontend/src/app/_pages/experiment/experiment.component.ts @@ -1,9 +1,15 @@ -import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, ViewChild, ViewChildren, Input } from '@angular/core'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { MatStepper } from '@angular/material/stepper'; import Shared from 'src/app/Shared'; -import { FolderType } from 'src/app/_data/FolderFile'; -import { TabType } from 'src/app/_elements/folder/folder.component'; +import { FolderFile, FolderType } from 'src/app/_data/FolderFile'; +import { FolderComponent, TabType } from 'src/app/_elements/folder/folder.component'; +import Experiment from 'src/app/_data/Experiment'; +import { ExperimentsService } from 'src/app/_services/experiments.service'; +import { ModelsService } from 'src/app/_services/models.service'; +import Model from 'src/app/_data/Model'; +import Dataset from 'src/app/_data/Dataset'; +import { ColumnTableComponent } from 'src/app/_elements/column-table/column-table.component'; @Component({ selector: 'app-experiment', @@ -17,8 +23,28 @@ export class ExperimentComponent implements AfterViewInit { @ViewChildren('steps') steps!: ElementRef[]; event: number = 0; + experiment: Experiment; + dataset?: Dataset; + @ViewChild("folderDataset") folderDataset!: FolderComponent; + @ViewChild("folderModel") folderModel!: FolderComponent; + @ViewChild(ColumnTableComponent) columnTable!: ColumnTableComponent; - constructor() { } + + constructor(private experimentsService: ExperimentsService, private modelsService: ModelsService) { + this.experiment = new Experiment("exp1"); + } + + /*updateExperiment(){ + + }*/ + + addNewExperiment() { + this.experimentsService.addExperiment(this.experiment).subscribe(() => { console.log("new Experiment") }); + } + + trainModel() { + this.modelsService.trainModel((<Model>this.folderModel.selectedFile)._id, this.experiment._id).subscribe(() => { console.log("pocelo treniranje") }) + } stepHeight = this.calcStepHeight(); @@ -92,4 +118,16 @@ export class ExperimentComponent implements AfterViewInit { TabType = TabType; + columnTableChangedEvent() { + //sta se desi kad se nesto promeni u column-table komponenti... + console.log("promenio se column-table"); + } + + setDataset(dataset: FolderFile) { + const d = <Dataset>dataset; + this.experiment.datasetId = d._id; + this.dataset = d; + + this.columnTable.loadDataset(this.dataset); + } } diff --git a/frontend/src/app/_pages/home/home.component.css b/frontend/src/app/_pages/home/home.component.css index 22137c24..906f5728 100644 --- a/frontend/src/app/_pages/home/home.component.css +++ b/frontend/src/app/_pages/home/home.component.css @@ -15,6 +15,6 @@ h1 { .card { margin: 2.5rem !important; - padding: 3rem; + padding: 1.5rem; width: 26rem !important; }
\ No newline at end of file diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html index 508382da..2825b3bf 100644 --- a/frontend/src/app/_pages/home/home.component.html +++ b/frontend/src/app/_pages/home/home.component.html @@ -27,6 +27,7 @@ </p> </div> </div> - </div> + + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_services/csv-parse.service.ts b/frontend/src/app/_services/csv-parse.service.ts index 4a05535a..aae10193 100644 --- a/frontend/src/app/_services/csv-parse.service.ts +++ b/frontend/src/app/_services/csv-parse.service.ts @@ -1,4 +1,10 @@ import { Injectable } from "@angular/core"; +import * as FileSaver from 'file-saver'; +import * as XLSX from 'xlsx'; + +const EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; +const EXCEL_EXTENSION = '.xlsx'; + @Injectable({ providedIn: 'root' }) export class CsvParseService { @@ -47,10 +53,42 @@ export class CsvParseService { if (strMatchedValue.length > 0) arrData[arrData.length - 1].push(strMatchedValue); - else + else arrData[arrData.length - 1].push(null); } return (arrData); } + + ConvertJSONToCSV(objArray: string, headerList: { [x: string]: any; }) { + let array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray; + let str = ''; + let row = 'S.No,'; + for (let index in headerList) { + row += headerList[index] + ','; + } + row = row.slice(0, -1); + str += row + '\r\n'; + for (let i = 0; i < array.length; i++) { + let line = (i + 1) + ''; + for (let index in headerList) { + let head = headerList[index]; + line += ',' + array[i][head]; + } + str += line + '\r\n'; + } + return str; + } + + public exportAsExcelFile(json: any[], excelFileName: string): void { + const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(json); + const workbook: XLSX.WorkBook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] }; + const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); + this.saveAsExcelFile(excelBuffer, excelFileName); + } + + private saveAsExcelFile(buffer: any, fileName: string): void { + const data: Blob = new Blob([buffer], { type: EXCEL_TYPE }); + FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION); + } }
\ No newline at end of file diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index 3b6e6b64..d3f646cb 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -25,10 +25,10 @@ export class DatasetsService { } getDatasetFile(fileId: any): any { - return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/true/${fileId}`, { headers: this.authService.authHeader(), responseType: 'text' }); + return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}`, { headers: this.authService.authHeader(), responseType: 'text' }); } getDatasetFilePartial(fileId: any, startRow: number, rowNum: number): Observable<any> { - return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/true/${fileId}/${startRow}/${rowNum}`, { headers: this.authService.authHeader(), responseType: 'text' }); + return this.http.get(`${Configuration.settings.apiURL}/file/csvRead/${fileId}/${startRow}/${rowNum}`, { headers: this.authService.authHeader(), responseType: 'text' }); } editDataset(dataset: Dataset): Observable<Dataset> { diff --git a/frontend/src/app/_services/experiments.service.ts b/frontend/src/app/_services/experiments.service.ts index bdaf62a7..29569fca 100644 --- a/frontend/src/app/_services/experiments.service.ts +++ b/frontend/src/app/_services/experiments.service.ts @@ -19,4 +19,8 @@ export class ExperimentsService { getMyExperiments(): Observable<Experiment[]> { return this.http.get<Experiment[]>(`${Configuration.settings.apiURL}/experiment/getmyexperiments`, { headers: this.authService.authHeader() }); } + + updateExperiment(experiment: Experiment): Observable<Experiment> { + return this.http.put<Experiment>(`${Configuration.settings.apiURL}/experiment/` + experiment._id, experiment, { headers: this.authService.authHeader() }); + } } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index fc6d3c6b..d44bf6ad 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -21,6 +21,7 @@ import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.compon import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.component'; import { EncodingDialogComponent } from './_modals/encoding-dialog/encoding-dialog.component'; import { MissingvaluesDialogComponent } from './_modals/missingvalues-dialog/missingvalues-dialog.component'; +import { SaveExperimentDialogComponent } from './_modals/save-experiment-dialog/save-experiment-dialog.component'; // Pages import { HomeComponent } from './_pages/home/home.component'; import { ProfileComponent } from './_pages/profile/profile.component'; @@ -32,6 +33,7 @@ import { ScatterchartComponent } from './_elements/_charts/scatterchart/scatterc import { BarchartComponent } from './_elements/_charts/barchart/barchart.component'; import { PieChartComponent } from './_elements/_charts/pie-chart/pie-chart.component'; import { BoxPlotComponent } from './_elements/_charts/box-plot/box-plot.component'; +import {LineChartComponent} from './_elements/_charts/line-chart/line-chart.component'; // Elements import { NavbarComponent } from './_elements/navbar/navbar.component'; import { NotificationsComponent } from './_elements/notifications/notifications.component'; @@ -49,6 +51,7 @@ import { TestComponent } from './_pages/test/test.component'; import { DoughnutChartComponent } from './_elements/_charts/doughnut-chart/doughnut-chart.component'; import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component'; import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; +import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); @@ -85,7 +88,10 @@ export function initializeApp(appConfig: Configuration) { MissingvaluesDialogComponent, TestComponent, DoughnutChartComponent, - HeatmapComponent + HeatmapComponent, + MetricViewComponent, + LineChartComponent, + SaveExperimentDialogComponent ], imports: [ BrowserModule, |