diff options
8 files changed, 210 insertions, 28 deletions
diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/graph/graph.component.css diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html new file mode 100644 index 00000000..527d3f1a --- /dev/null +++ b/frontend/src/app/_elements/graph/graph.component.html @@ -0,0 +1,3 @@ +<div id="graphWrapper" class="w-100" style="height: 16rem;"> + <canvas id="graphCanvas" class="border"></canvas> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.spec.ts b/frontend/src/app/_elements/graph/graph.component.spec.ts new file mode 100644 index 00000000..99783d42 --- /dev/null +++ b/frontend/src/app/_elements/graph/graph.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GraphComponent } from './graph.component'; + +describe('GraphComponent', () => { + let component: GraphComponent; + let fixture: ComponentFixture<GraphComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GraphComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(GraphComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/graph/graph.component.ts b/frontend/src/app/_elements/graph/graph.component.ts new file mode 100644 index 00000000..c17e8906 --- /dev/null +++ b/frontend/src/app/_elements/graph/graph.component.ts @@ -0,0 +1,145 @@ +import { Component, Input, OnInit } from '@angular/core'; +import Dataset from 'src/app/_data/Dataset'; +import Model from 'src/app/_data/Model'; + +@Component({ + selector: 'app-graph', + templateUrl: './graph.component.html', + styleUrls: ['./graph.component.css'] +}) +export class GraphComponent implements OnInit { + + @Input() model?: Model; + @Input() inputCols: number = 1; + + @Input() lineThickness: number = 5; + @Input() nodeRadius: number = 15; + @Input() lineColor: string = '#ff0000'; + @Input() nodeColor: string = '#222277'; + @Input() inputNodeColor: string = '#44ee22'; + @Input() outputNodeColor: string = '#559977'; + + private wrapper?: HTMLDivElement; + private canvas?: HTMLCanvasElement; + private ctx?: CanvasRenderingContext2D; + + constructor() { } + + ngOnInit(): void { + this.wrapper = (<HTMLDivElement>document.getElementById('graphWrapper')); + this.canvas = (<HTMLCanvasElement>document.getElementById('graphCanvas')); + const ctx = this.canvas.getContext('2d'); + if (ctx) { + this.ctx = ctx; + } else { + console.warn('Could not get canvas context!'); + } + + window.addEventListener('resize', () => { this.resize() }); + this.update(); + this.resize(); + + /*setInterval(() => { + this.update(); + }, 5000);*/ + } + + layers?: Node[][]; + + update() { + this.layers = []; + + let inputNodeIndex = 0; + const inputLayer: Node[] = []; + while (inputNodeIndex < this.inputCols) { + const x = 0.5 / (this.model!.hiddenLayers + 2); + const y = (inputNodeIndex + 0.5) / this.inputCols; + const node = new Node(x, y, this.inputNodeColor); + inputLayer.push(node); + inputNodeIndex += 1; + } + this.layers.push(inputLayer); + + let layerIndex = 1; + while (layerIndex < this.model!.hiddenLayers + 1) { + const newLayer: Node[] = []; + let nodeIndex = 0; + while (nodeIndex < this.model!.hiddenLayerNeurons) { + const x = (layerIndex + 0.5) / (this.model!.hiddenLayers + 2); + const y = (nodeIndex + 0.5) / this.model!.hiddenLayerNeurons; + const node = new Node(x, y, this.nodeColor); + newLayer.push(node); + nodeIndex += 1; + } + this.layers.push(newLayer); + layerIndex += 1; + } + + const outX = 1 - (0.5 / (this.model!.hiddenLayers + 2)); + const outY = 0.5; + this.layers.push([new Node(outX, outY, this.outputNodeColor)]) + this.draw(); + } + + draw() { + this.ctx!.clearRect(0, 0, this.canvas!.width, this.canvas!.height); + + let index = 0; + while (index < this.layers!.length - 1) { + for (let node1 of this.layers![index]) { + for (let node2 of this.layers![index + 1]) { + this.drawLine(node1, node2); + } + } + index += 1; + } + + for (let layer of this.layers!) { + for (let node of layer) { + this.drawNode(node); + } + } + } + + drawLine(node1: Node, node2: Node) { + this.ctx!.strokeStyle = this.lineColor; + this.ctx!.beginPath(); + this.ctx!.moveTo(node1.x * this.width, node1.y * this.height); + this.ctx!.lineTo(node2.x * this.width, node2.y * this.height); + this.ctx!.stroke(); + } + + drawNode(node: Node) { + this.ctx!.fillStyle = node.color; + this.ctx!.strokeStyle = '#000'; + this.ctx!.beginPath(); + this.ctx!.arc(node.x * this.width, node.y * this.height, this.nodeRadius, 0, 2 * Math.PI); + this.ctx!.fill(); + this.ctx!.stroke(); + } + + width = 200; + height = 200; + ratio = 1; + + resize() { + this.width = this.wrapper!.offsetWidth; + this.height = this.wrapper!.offsetHeight; + this.ratio = this.width / this.height; + + if (this.canvas) { + this.canvas.width = this.width; + this.canvas.height = this.height; + } + + this.draw(); + } +} + +class Node { + constructor( + public x: number, + public y: number, + public color: string + ) { } +} diff --git a/frontend/src/app/_elements/item-model/item-model.component.html b/frontend/src/app/_elements/item-model/item-model.component.html index c8c1a36d..695c580e 100644 --- a/frontend/src/app/_elements/item-model/item-model.component.html +++ b/frontend/src/app/_elements/item-model/item-model.component.html @@ -1,4 +1,3 @@ - <div class="card" style="min-width: 12rem;"> <div class="card-header"> {{model.name}} @@ -9,6 +8,6 @@ {{"Datum kreiranja: " + model.dateCreated}}<br> {{"Poslednje ažuriranje: " + model.lastUpdated}}<br> </p> - <app-annvisual class="align-items-center" [model]="model" style="width: 12rem"></app-annvisual> + <app-graph [model]="model"></app-graph> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/model-load/model-load.component.html b/frontend/src/app/_elements/model-load/model-load.component.html index 62f0932f..0923c895 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.html +++ b/frontend/src/app/_elements/model-load/model-load.component.html @@ -1,4 +1,3 @@ - <div> <div class="col-12 d-flex my-5"> <div class="col-3"> @@ -15,28 +14,27 @@ </div> <div *ngIf="showMyModels" class="px-5 my-2"> - <input *ngIf="showMyModels" type="text" class="form-control" placeholder="Pretraga" - [(ngModel)]="term"> + <input *ngIf="showMyModels" type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> </div> <div *ngIf="showMyModels" class="px-5"> <div class="overflow-auto" style="max-height: 500px;"> - <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let model of myModels|filter:term" - [ngClass]="{'selectedModelClass': this.selectedModel == model}"> - <app-item-model name="usersModel" [model]="model" - (click)="selectThisModel(model);"></app-item-model> - </li> - </ul> - <div class="px-5 mt-5"> - <!--prikaz izabranog modela--> - </div> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let model of myModels|filter:term" + [ngClass]="{'selectedModelClass': this.selectedModel == model}"> + <app-item-model name="usersModel" [model]="model" (click)="selectThisModel(model);"> + </app-item-model> + </li> + </ul> + <div class="px-5 mt-5"> + <!--prikaz izabranog modela--> + </div> </div> </div> <div *ngIf="!showMyModels"> <div class="form-group row mt-3 mb-2 d-flex justify-content-center"> - + <div class="col-3"> <label for="name" class="col-form-label">Naziv modela:</label> <input type="text" class="form-control" name="name" placeholder="Naziv..." [(ngModel)]="newModel.name"> @@ -79,7 +77,8 @@ <div class="col-1"> <input type="number" min="1" class="form-control" name="hiddenLayers" [(ngModel)]="newModel.hiddenLayers" - (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])"> + (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])" + (ngModelChange)="updateGraph()"> </div> </div> @@ -105,7 +104,7 @@ </div> <div class="col-1"> <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" - [(ngModel)]="newModel.hiddenLayerNeurons"> + [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> </div> </div> @@ -117,7 +116,8 @@ </div> <div class="col-2"> <select id=optimizerOptions class="form-control" name="optimizer" [(ngModel)]="newModel.optimizer"> - <option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" + <option + *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> {{ optionName }} </option> @@ -151,8 +151,8 @@ <div class="col-1"></div> <div class="col-3 mt-2"> <label for="type" class="form-check-label">Nasumičan redosled podataka?</label> - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="newModel.randomOrder" type="checkbox" - value="" checked> + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="newModel.randomOrder" + type="checkbox" value="" checked> </div> </div> <div class="border m-3"> @@ -191,7 +191,8 @@ <div class="row p-2 m-2" style="align-self: center;"> <div class="col-3"> - <label for="hiddenLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija + <label for="hiddenLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije skrivenih slojeva:</label> </div> @@ -213,7 +214,8 @@ </div> </div> <div class="col-3"> - <label for="outputLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija + <label for="outputLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije izlaznog sloja:</label> </div> @@ -248,9 +250,11 @@ </div> </div> </div> + <app-graph [model]="newModel" [inputCols]="1"></app-graph> <div class="form-group row mt-5 mb-3"> <div class="col"></div> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="uploadModel();">Sačuvaj + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" + (click)="uploadModel();">Sačuvaj model</button> <div class="col"></div> </div> diff --git a/frontend/src/app/_elements/model-load/model-load.component.ts b/frontend/src/app/_elements/model-load/model-load.component.ts index 6db40916..ca6b8ea5 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.ts +++ b/frontend/src/app/_elements/model-load/model-load.component.ts @@ -1,9 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; import Shared from 'src/app/Shared'; import Model, { ActivationFunction, Encoding, LossFunction, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType } from 'src/app/_data/Model'; import { ModelsService } from 'src/app/_services/models.service'; -import { Output } from '@angular/core'; -import { EventEmitter } from '@angular/core'; +import { GraphComponent } from '../graph/graph.component'; @Component({ @@ -13,6 +12,7 @@ import { EventEmitter } from '@angular/core'; }) export class ModelLoadComponent implements OnInit { + @ViewChild(GraphComponent) graph!: GraphComponent; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); newModel: Model = new Model(); @@ -46,6 +46,10 @@ export class ModelLoadComponent implements OnInit { ngOnInit(): void { } + updateGraph() { + this.graph.update(); + } + getMetrics() { this.newModel.metrics = []; let cb = document.getElementsByName("cbmetrics"); diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c3a2ce7a..0c1a4162 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -43,6 +43,7 @@ import { LoadingComponent } from './_elements/loading/loading.component'; import { ModelLoadComponent } from './_elements/model-load/model-load.component'; import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; import { AddNewDatasetComponent } from './_elements/add-new-dataset/add-new-dataset.component'; +import { GraphComponent } from './_elements/graph/graph.component'; @NgModule({ declarations: [ @@ -75,7 +76,8 @@ import { AddNewDatasetComponent } from './_elements/add-new-dataset/add-new-data LoadingComponent, ModelLoadComponent, AlertDialogComponent, - AddNewDatasetComponent + AddNewDatasetComponent, + GraphComponent ], imports: [ BrowserModule, |