diff options
Diffstat (limited to 'frontend/src/app/_elements')
13 files changed, 369 insertions, 270 deletions
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 5dc2e9ec..aee2314e 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.css +++ b/frontend/src/app/_elements/column-table/column-table.component.css @@ -55,8 +55,8 @@ mat-slider { } #missingValuesHeader { - font-size: 12px; - line-height: 110% !important; + font-size: 13px; + line-height: 140% !important; } .verticalAlign { @@ -87,7 +87,7 @@ table ::ng-deep .mat-form-field-wrapper { } .no-pad { - padding: 1px; + padding: 2px; margin: 0; } @@ -242,4 +242,24 @@ col:not(.col-disabled) { .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 42c43138..53372574 100644 --- a/frontend/src/app/_elements/column-table/column-table.component.html +++ b/frontend/src/app/_elements/column-table/column-table.component.html @@ -100,10 +100,11 @@ <tr> <th>Tip</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)]="colInfo.isNumber" [disabled]="!columnsChecked[i]"> - <mat-option [value]="false">Kategorijski</mat-option> - <mat-option [value]="true">Numerički</mat-option> + <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)="columnTableChangeDetected()"> + <mat-option [value]="ColumnType.categorical">Kategorijski</mat-option> + <mat-option [value]="ColumnType.numerical">Numerički</mat-option> </mat-select> </mat-form-field> </td> @@ -111,38 +112,36 @@ <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.isNumber" [width]="150" [height]="150"></app-box-plot> - <app-pie-chart *ngIf="!colInfo.isNumber" [width]="150" [height]="150"></app-pie-chart> + <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]}"> - <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"> + <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"> - {{uniqueValue}}<br><!-- TODO na ML-u: broj ponavljanja unique values-a u zagradi nek pise --> - </span> + ({{colInfo.uniqueValuesPercent[i].toFixed(4)}}%) {{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">settings</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]"> + <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> @@ -152,94 +151,74 @@ </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> + <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]}"> - <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</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 *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> - <!--<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 *ngIf="dataset && experiment" 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 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="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> - </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 [(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="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 [(value)]="experiment.outputColumn" (selectionChange)="changeOutputColumn()"> + <mat-option *ngFor="let inputColumn of experiment.inputColumns" [value]="inputColumn">{{inputColumn}}</mat-option> </mat-select> </mat-form-field> </div> diff --git a/frontend/src/app/_elements/column-table/column-table.component.ts b/frontend/src/app/_elements/column-table/column-table.component.ts index 4499196c..01e4e564 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,13 @@ 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'; @Component({ selector: 'app-column-table', @@ -20,22 +20,28 @@ export class ColumnTableComponent implements AfterViewInit { @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[] = []; columnsChecked: boolean[] = []; //niz svih kolona - testSetDistribution: number = 70; + constructor(private datasetService: DatasetsService, 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.dataset = datasets[2]; + + this.setColumnTypeInitial(); this.experiment = new Experiment(); this.dataset.columnInfo.forEach(column => { this.columnsChecked.push(true); @@ -44,8 +50,9 @@ export class ColumnTableComponent implements AfterViewInit { 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.setDeleteColumnsForMissingValTreatment(); + this.setDeleteRowsForMissingValTreatment(); this.nullValOption = [].constructor(this.dataset.columnInfo.length).fill('Obriši redove'); @@ -57,7 +64,15 @@ export class ColumnTableComponent implements AfterViewInit { }); } - setDeleteColumnsForMissingValTreatment() { + 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 = []; @@ -71,8 +86,13 @@ export class ColumnTableComponent implements AfterViewInit { } } + columnTableChangeDetected() { + this.columnTableChanged.emit(); + } + 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); @@ -84,7 +104,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() { + 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(); } } @@ -95,6 +134,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() { @@ -131,9 +171,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() { @@ -145,13 +187,11 @@ export class ColumnTableComponent implements AfterViewInit { this.resetMissingValuesTreatment(selectedMissingValuesOption); }); } - updateTestSet(event: MatSliderChange) { - this.testSetDistribution = event.value!; - } + 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]; @@ -167,7 +207,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(); } } @@ -189,6 +231,7 @@ export class ColumnTableComponent implements AfterViewInit { } this.nullValOption[index] = "Popuni sa: " + fillValue; + this.columnTableChangeDetected(); } } getValue(columnName: string): string { 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 54c7a3d7..e77f837e 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"> @@ -49,35 +53,40 @@ </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="folder-inside bg-blur"> + <div class="file-content" [ngClass]="{'form-hidden' : listView}"> <div class="file-bottom-buttons"> <button class="btn-clear file-button" (click)="deleteFile()"> <mat-icon>delete</mat-icon> </button> <button class="btn-clear file-button"> - <mat-icon>link</mat-icon> + <mat-icon>share</mat-icon> </button> <button class="btn-clear file-button"> <mat-icon>zoom_out_map</mat-icon> </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 [forExperiment]="forExperiment" [model]="fileToDisplay" [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 a1cd6075..e0336ded 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -1,4 +1,4 @@ -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'; @@ -15,7 +15,7 @@ import { PredictorsService } from 'src/app/_services/predictors.service'; templateUrl: './folder.component.html', styleUrls: ['./folder.component.css'] }) -export class FolderComponent implements OnInit { +export class FolderComponent implements AfterViewInit { @ViewChild(FormDatasetComponent) formDataset?: FormDatasetComponent; @@ -23,14 +23,13 @@ export class FolderComponent implements OnInit { @Input() folderName: string = 'Moji podaci'; - @Input() files!: FolderFile[] newFile!: Dataset | Model; @Input() type: FolderType = FolderType.Dataset; - @Input() forExperiment?: Experiment; + @Input() startingTab: TabType = TabType.MyDatasets; newFileSelected: boolean = true; @@ -53,26 +52,21 @@ export class FolderComponent implements OnInit { this.folders[TabType.File] = []; this.folders[TabType.NewFile] = []; - this.refreshFiles(); - - } - ngOnInit(): void { - if (this.files.length > 0) - this.selectFile(0); - else { - this.selectNewFile(); - } + ngAfterViewInit(): void { + this.refreshFiles(); } - displayFile(){ - if(this.type == FolderType.Dataset) + _initialized = false; + + displayFile() { + if (this.type == FolderType.Dataset) this.formDataset!.dataset = <Dataset>this.fileToDisplay; } hoverOverFile(i: number) { - this.hoveringOverFileIndex = i; + /*this.hoveringOverFileIndex = i; if (i != -1) { this.fileToDisplay = this.files[i]; } else { @@ -82,7 +76,7 @@ export class FolderComponent implements OnInit { this.fileToDisplay = this.files[this.selectedFileIndex]; } } - this.displayFile(); + this.displayFile();*/ } selectNewFile() { @@ -90,20 +84,20 @@ 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() { @@ -118,7 +112,11 @@ export class FolderComponent implements OnInit { this.okPressed.emit(); } - refreshFiles(){ + refreshFiles() { + this.tabsToShow.forEach(tab => { + this.folders[tab] = []; + }) + this.datasetsService.getMyDatasets().subscribe((datasets) => { this.folders[TabType.MyDatasets] = datasets; }); @@ -144,17 +142,16 @@ export class FolderComponent implements OnInit { this.folders[TabType.MyExperiments] = experiments; }); - this.files = []; - - this.filteredFiles.length = 0; - this.filteredFiles.push(...this.files); - - this.searchTermsChanged(); - + if (!this._initialized) { + this.selectTab(this.startingTab); + this._initialized = true; + } + else + this.searchTermsChanged(); } saveNewFile() { - if(this.type == FolderType.Dataset) + if (this.type == FolderType.Dataset) this.formDataset!.uploadDataset(); } @@ -167,7 +164,7 @@ export class FolderComponent implements OnInit { zIndex = this.files.length + 3; return zIndex; } - + newFileZIndex() { return (this.files.length + 1); }*/ @@ -182,16 +179,19 @@ export class FolderComponent implements OnInit { searchTermsChanged() { 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; @@ -214,51 +214,92 @@ 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); + if (tab == TabType.NewFile) { + + this.selectNewFile(); + } + + this.listView = this.getListView(tab); + this.type = this.getFolderType(tab); + this.previousPrivacy = this.privacy; + this.privacy = this.getPrivacy(tab); this.selectedTab = tab; this.files = this.folders[tab]; - this.searchTermsChanged(); + 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; + } + } + + previousPrivacy: Privacy = Privacy.Private; + + getPrivacy(tab: TabType) { + switch (tab) { + case TabType.PublicDatasets: + case TabType.PublicModels: + return Privacy.Public; + case TabType.None: + return this.previousPrivacy; + default: + return Privacy.Private; } } hoverOverTab(tab: TabType) { - this.checkListView(tab); + this.listView = this.getListView(tab); + this.previousPrivacy = this.privacy; + 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 7bbe0921..80476292 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, .xlsx, .json"> - - - <div class="mt-5 datatable"> - <app-datatable [tableData]="tableData"></app-datatable> - </div> - - - </div> - +<div class="folderBox" *ngIf="dataset"> + + <div class="row"> + <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}}" [(ngModel)]="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"> + <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 c7efe098..5e088e46 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -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; 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..9b55a814 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,12 @@ hr { .m-2 { max-height: 20 rem; -}
\ No newline at end of file +} + +mat-slider { + width: 40%; +} + +.slider { + background-color: var(--ns-bg-dark-100); +} 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..e51c2cac 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -85,7 +85,23 @@ </div> </div> +<div> + <div class="ns-row" style="margin-top: 10px;"> + <div class="ns-col slider rounded" style="border:1px solid var(--ns-primary);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="ns-col slider rounded text-offwhite justify-content-center align-items-center" style="border:1px solid var(--ns-primary);margin-left: 10px;"> + <mat-checkbox class="pt-4" color="accent">Nasumični redosled podataka</mat-checkbox> + </div> + + </div> +</div> <!--kraj unosa parametara--> <hr> @@ -199,4 +215,5 @@ </mat-select> </mat-form-field> </div> -</div>
\ No newline at end of file +</div> + 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..d5c497aa 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', @@ -15,7 +15,7 @@ export class FormModelComponent implements AfterViewInit { @ViewChild(GraphComponent) graph!: GraphComponent; @Input() forExperiment?: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); - + testSetDistribution: number = 70; constructor() { } ngAfterViewInit(): void { @@ -132,6 +132,9 @@ export class FormModelComponent implements AfterViewInit { this.updateGraph(); } } + updateTestSet(event: MatSliderChange) { + this.testSetDistribution = event.value!; + } |