{
+ const response: Response = await fetch(path);
+ if (!response.ok) throw new Error(`Failed to load weight data from ${path}`);
+ return await response.json();
+}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 807ff3c..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import AppController from './controller/AppContoller.js';
-import { DataStore } from "./controller/DataStore.js";
-
-const App = new AppController({DataStore});
-await App.initialize();
-console.log("App initialized!");
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..70a5ce8
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,5 @@
+import AppController from './controller/AppController.js';
+
+const App = new AppController();
+await App.initialize();
+console.log('App initialized!');
diff --git a/src/view/ViewHandler.js b/src/view/ViewHandler.js
deleted file mode 100644
index e2ded8b..0000000
--- a/src/view/ViewHandler.js
+++ /dev/null
@@ -1,7 +0,0 @@
-class ViewHandler {
- constructor() {
-
- }
-}
-
-export default ViewHandler;
\ No newline at end of file
diff --git a/src/view/ViewPresenter.ts b/src/view/ViewPresenter.ts
new file mode 100644
index 0000000..2a703ef
--- /dev/null
+++ b/src/view/ViewPresenter.ts
@@ -0,0 +1,38 @@
+import eventBus from '@/controller/EventBus.ts';
+import { DATA_EVENTS, DRAWING_EVENTS } from '@/controller/constants/events.ts';
+
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+
+class ViewPresenter {
+ private readonly El: HTMLElement | null;
+
+ constructor() {
+ this.El = document.getElementById('result') as HTMLDivElement;
+ this.normalizeResult();
+ this.renderResult(undefined);
+ }
+
+ normalizeResult() {
+ eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D) => {
+ const queryResults = data.flatMap((v: number[], index) => v[0] * 100);
+ const result = queryResults.reduce(
+ (acc: number, cur: number, index: number, arr: number[]) => {
+ return arr[acc] < cur ? index : acc;
+ },
+ 0,
+ );
+ // const result: number = Math.max(...queryResults);
+ this.renderResult(result);
+ });
+ eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => {
+ this.renderResult(undefined);
+ });
+ }
+
+ renderResult(result: number | undefined): void {
+ const networkAnswer =
+ (this.El.innerHTML = `${result == undefined ? '?' : result}
`);
+ }
+}
+
+export default ViewPresenter;
diff --git a/src/view/components/CanvasComponentBase.js b/src/view/components/CanvasComponentBase.js
index bf544ff..2af8a95 100644
--- a/src/view/components/CanvasComponentBase.js
+++ b/src/view/components/CanvasComponentBase.js
@@ -30,7 +30,6 @@ class CanvasComponentBase {
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
-
}
-export default CanvasComponentBase;
\ No newline at end of file
+export default CanvasComponentBase;
diff --git a/src/view/components/canvasUtils/BoundingBox.js b/src/view/components/canvasUtils/BoundingBox.js
deleted file mode 100644
index 7fe4911..0000000
--- a/src/view/components/canvasUtils/BoundingBox.js
+++ /dev/null
@@ -1,38 +0,0 @@
-class BoundingBox {
- constructor() {
- this.initialCoords = {
- minX: Infinity,
- minY: Infinity,
- maxX: -Infinity,
- maxY: -Infinity,
- }
- this.coordinate = {...this.initialCoords};
- this.originalObject = {};
- }
-
- update(currentX, currentY) {
- this.coordinate = {
- minX: Math.round(Math.min(this.coordinate.minX, currentX)),
- minY: Math.round(Math.min(this.coordinate.minY, currentY)),
- maxX: Math.round(Math.max(this.coordinate.maxX, currentX)),
- maxY: Math.round(Math.max(this.coordinate.maxY, currentY)),
- }
- Object.assign(this.originalObject,
- {
- x: this.coordinate.minX-20,
- y: this.coordinate.minY-20,
- width: this.coordinate.maxX+40 - this.coordinate.minX,
- height: this.coordinate.maxY+40 - this.coordinate.minY,});
- }
-
- log() {
- console.log(this.coordinate);
- console.log(this.originalObject);
- }
-
- reset() {
- this.coordinate = {...this.initialCoords};
- }
-}
-
-export default new BoundingBox;
\ No newline at end of file
diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts
new file mode 100644
index 0000000..bd67631
--- /dev/null
+++ b/src/view/components/perceptron/BaseCanvas.ts
@@ -0,0 +1,66 @@
+import eventBus from '@/controller/EventBus.ts';
+import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts';
+import { nodePath } from '@/view/components/perceptron/shapeVector/nodePath.ts';
+
+class BaseCanvas {
+ private readonly canvas: HTMLCanvasElement | null;
+ private readonly ctx: CanvasRenderingContext2D;
+ private canvasCenter: { x: number; y: number };
+
+ private setupMatrix: any;
+
+ constructor() {
+ this.canvas = document.getElementById('perceptron') as HTMLCanvasElement | null;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+
+ this.setupCanvas(); // canvas element setup
+ this.setupRenderTransform();
+ // this.setupRenderTransform(this.ctx, RENDERER_CONFIG.degree); // apply state for rendering context(transform/rotate..)
+ }
+
+ getCanvas = (): HTMLCanvasElement => this.canvas;
+ getCtx = (): CanvasRenderingContext2D => this.ctx;
+
+ setupCanvas() {
+ this.canvas.width = 2000;
+ this.canvas.height = 720;
+
+ this.ctx.fillStyle = 'rgb(255,255,255)';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ this.canvasCenter = { x: this.canvas.width / 2, y: this.canvas.height };
+ }
+
+ setupRenderTransform(): void {
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
+ this.ctx.translate(this.canvasCenter.x, this.canvasCenter.y);
+ this.rotateCanvas(true, RENDERER_CONFIG.degree * 180);
+ this.setupMatrix = this.ctx.getTransform();
+ console.log('셋업 렌더 셋팅을 실행함.');
+ }
+
+ rotateCanvas = (clockWise: boolean, value: number) => {
+ const rotationVector = clockWise ? 1 : -1;
+ this.ctx.rotate(rotationVector * value);
+ };
+ moveCanvas = (x: number, y: number) => {
+ this.ctx.translate(x, y);
+ };
+
+ clearCanvas() {
+ this.saveState();
+ this.ctx.translate(-this.canvasCenter.x, -this.canvasCenter.y);
+ this.ctx.fillStyle = 'rgb(255,255,255)';
+ this.ctx.fillRect(0, 0, this.canvasCenter.x * 2, this.canvasCenter.y * 2);
+ this.restoreState();
+ }
+
+ saveState() {
+ this.ctx.save();
+ }
+
+ restoreState() {
+ this.ctx.restore();
+ }
+}
+
+export default BaseCanvas;
diff --git a/src/view/components/perceptron/EdgeRenderer.ts b/src/view/components/perceptron/EdgeRenderer.ts
new file mode 100644
index 0000000..52e3052
--- /dev/null
+++ b/src/view/components/perceptron/EdgeRenderer.ts
@@ -0,0 +1,25 @@
+class EdgeRenderer {
+ private renderCtx: CanvasRenderingContext2D;
+
+ constructor(ctx: CanvasRenderingContext2D) {
+ this.renderCtx = ctx;
+ }
+
+ drawEdge = (aX: number, aY: number, bX: number, bY: number): void => {
+ const dX: number = Math.abs(aX - bX);
+ const dY: number = Math.abs(aY - bY);
+ const distance = Math.sqrt(dX * dX + dX * dY);
+ if (distance > 490) return;
+
+ this.renderCtx.strokeStyle = '#000000';
+ this.renderCtx.lineWidth = 0.3;
+
+ this.renderCtx.beginPath();
+ this.renderCtx.moveTo(aX, aY);
+ this.renderCtx.lineTo(bX, bY);
+ this.renderCtx.stroke();
+ this.renderCtx.moveTo(aX, aY);
+ };
+}
+
+export default EdgeRenderer;
diff --git a/src/view/components/perceptron/GridRenderer.ts b/src/view/components/perceptron/GridRenderer.ts
new file mode 100644
index 0000000..cc43971
--- /dev/null
+++ b/src/view/components/perceptron/GridRenderer.ts
@@ -0,0 +1,28 @@
+class GridRenderer {
+ private renderCtx: CanvasRenderingContext2D;
+
+ constructor(ctx: CanvasRenderingContext2D) {
+ this.renderCtx = ctx;
+ }
+
+ drawGrid(position: number, gridLength: number): void {
+ this.renderCtx.strokeStyle = '#c3c3c3';
+ this.renderCtx.lineWidth = 1;
+
+ this.renderCtx.save();
+
+ this.renderCtx.beginPath();
+ this.renderCtx.moveTo(0, position);
+ this.renderCtx.lineTo(0, position + gridLength);
+ this.renderCtx.stroke();
+ this.renderCtx.restore();
+ }
+
+ drawArc(radius: number): void {
+ this.renderCtx.beginPath();
+ this.renderCtx.arc(0, 0, radius, 0, Math.PI * 2);
+ this.renderCtx.stroke();
+ }
+}
+
+export default GridRenderer;
diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts
new file mode 100644
index 0000000..3c9ea3e
--- /dev/null
+++ b/src/view/components/perceptron/NodeRenderer.ts
@@ -0,0 +1,41 @@
+import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts';
+import { nodePath } from '@/view/components/perceptron/shapeVector/nodePath.ts';
+
+class NodeRenderer {
+ private canvasCenter!: { x: number; y: number };
+ private rotationDelta!: number;
+ private degree!: number;
+ private displayNodes!: number;
+ private scrollSpeed!: number;
+
+ private mouseScroll!: number;
+ private touchMove!: number;
+ private touchDirection!: number;
+ private nodes!: number;
+
+ private renderCtx: CanvasRenderingContext2D;
+
+ constructor(ctx: CanvasRenderingContext2D) {
+ this.renderCtx = ctx;
+ }
+
+ initializeRenderState(RENDERER_CONFIG: rendererConfig): void {
+ this.rotationDelta = RENDERER_CONFIG.rotationDelta; // scrollHandler
+ this.degree = RENDERER_CONFIG.degree; // scrollHandler
+ this.displayNodes = RENDERER_CONFIG.displayNodes; // nodeHandler
+ this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler
+ }
+
+ drawNode = (x: number, y: number, angle: number, p: number): void => {
+ nodePath(this.renderCtx, {
+ x: x,
+ y: y,
+ width: 13,
+ height: 13,
+ angleOffset: angle,
+ percent: p,
+ });
+ };
+}
+
+export default NodeRenderer;
diff --git a/src/view/components/perceptron/objectClass/Edge.ts b/src/view/components/perceptron/objectClass/Edge.ts
new file mode 100644
index 0000000..b2ffa42
--- /dev/null
+++ b/src/view/components/perceptron/objectClass/Edge.ts
@@ -0,0 +1 @@
+class Edge {}
diff --git a/src/view/components/perceptron/objectClass/Node.ts b/src/view/components/perceptron/objectClass/Node.ts
new file mode 100644
index 0000000..254101c
--- /dev/null
+++ b/src/view/components/perceptron/objectClass/Node.ts
@@ -0,0 +1,14 @@
+import { value } from '@/view/components/perceptron/types/Node.ts';
+
+export class Node {
+ private value: Number;
+ constructor(value: number) {
+ this.value = value;
+ }
+ setValue(value: number): void {
+ this.value = value;
+ }
+ getValue(): number {
+ return this.value;
+ }
+}
diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts
new file mode 100644
index 0000000..871e606
--- /dev/null
+++ b/src/view/components/perceptron/shapeVector/nodePath.ts
@@ -0,0 +1,54 @@
+import { INodePath } from '@/view/components/perceptron/shape/types/nodePath.ts';
+
+export const nodePath = (
+ ctx: CanvasRenderingContext2D,
+ { x, y, width, height, radius = 5, angleOffset, percent }: INodePath,
+) => {
+ const node: Path2D = roundedRect(-width / 2, -height / 2, width, height, radius);
+
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate((angleOffset * Math.PI) / 180); // atan값이 반환하는 라디안 값을 degree로 변환
+
+ ctx.fillStyle = '#fff';
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1;
+ ctx.fill(node);
+ ctx.stroke(node);
+
+ if (percent > 0) {
+ const filledHeight = (height * percent) / 100;
+
+ const fillPath = roundedRect(-width / 2, -height / 2, filledHeight, height, radius);
+ ctx.save();
+ ctx.clip(node);
+ ctx.fillStyle = '#000';
+ ctx.fill(fillPath);
+ ctx.restore();
+ }
+
+ ctx.restore();
+};
+
+const roundedRect = (
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ radius: number,
+): Path2D => {
+ const path = new Path2D();
+ const r = Math.min(radius, width / 2, height / 2);
+
+ path.moveTo(x + r, y);
+ path.lineTo(x + width - r, y);
+ path.quadraticCurveTo(x + width, y, x + width, y + r);
+ path.lineTo(x + width, y + height - r);
+ path.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
+ path.lineTo(x + r, y + height);
+ path.quadraticCurveTo(x, y + height, x, y + height - r);
+ path.lineTo(x, y + r);
+ path.quadraticCurveTo(x, y, x + r, y);
+
+ return path;
+};
diff --git a/src/view/components/perceptron/shapeVector/types/nodePath.ts b/src/view/components/perceptron/shapeVector/types/nodePath.ts
new file mode 100644
index 0000000..6e8616b
--- /dev/null
+++ b/src/view/components/perceptron/shapeVector/types/nodePath.ts
@@ -0,0 +1,8 @@
+export interface INodePath {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ radius?: number;
+ percent: number;
+}
diff --git a/src/view/components/perceptron/types/Edge.ts b/src/view/components/perceptron/types/Edge.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/view/components/perceptron/types/Node.ts b/src/view/components/perceptron/types/Node.ts
new file mode 100644
index 0000000..3fd8de0
--- /dev/null
+++ b/src/view/components/perceptron/types/Node.ts
@@ -0,0 +1 @@
+export type value = number;
diff --git a/src/view/components/userQuery/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.js
deleted file mode 100644
index c5ae35e..0000000
--- a/src/view/components/userQuery/AlignCanvas.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import BoundingBox from "../canvasUtils/BoundingBox.js";
-
-class AlignCanvas {
- constructor() {
- // this.canvas = document.createElement('alignCanvas');
- this.canvas = document.getElementById('alignCanvas');
- this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
- this.canvas.style.border = '2px solid red';
-
- const { minX, minY, maxX, maxY } = BoundingBox.coordinate;
- this.setupCanvas();
- }
- setupCanvas() {
- this.ctx.fillStyle = 'rgba(0, 0, 0)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- this.updateCanvasScale();
- }
- updateCanvasScale() {
- if(BoundingBox.originalObject.width > BoundingBox.originalObject.height){
- this.canvas.width = BoundingBox.originalObject.width*1.3;
- this.canvas.height = BoundingBox.originalObject.width*1.3;
- } else {
- this.canvas.width = BoundingBox.originalObject.height*1.3;
- this.canvas.height = BoundingBox.originalObject.height*1.3;
- }
- }
-
- centralize(path) {
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- const alignStartPosition = {
- x: (this.canvas.width - BoundingBox.originalObject.width) / 2,
- y: (this.canvas.height - BoundingBox.originalObject.height) / 2,
- }
- this.ctx.drawImage(path,
- BoundingBox.originalObject.x, BoundingBox.originalObject.y,
- BoundingBox.originalObject.width, BoundingBox.originalObject.height,
- alignStartPosition.x, alignStartPosition.y,
- BoundingBox.originalObject.width, BoundingBox.originalObject.height
- );
- }
-}
-
-export default AlignCanvas;
\ No newline at end of file
diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts
new file mode 100644
index 0000000..25cbef0
--- /dev/null
+++ b/src/view/components/userQuery/AlignCanvas.ts
@@ -0,0 +1,53 @@
+import BoundingBox from '@/view/components/userQuery/canvasUtils/BoundingBox.ts';
+
+class AlignCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
+ constructor() {
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ this.canvas.style.border = '2px solid red';
+ this.setupCanvas();
+ }
+ setupCanvas(): void {
+ this.ctx.fillStyle = 'rgba(0, 0, 0)';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ this.updateCanvasScale();
+ }
+ updateCanvasScale(): void {
+ if (BoundingBox.getOriginalObject().width > BoundingBox.getOriginalObject().height) {
+ this.canvas.width = BoundingBox.getOriginalObject().width * 1.3;
+ this.canvas.height = BoundingBox.getOriginalObject().width * 1.3;
+ } else {
+ this.canvas.width = BoundingBox.getOriginalObject().height * 1.3;
+ this.canvas.height = BoundingBox.getOriginalObject().height * 1.3;
+ }
+ }
+
+ centralize(path: HTMLCanvasElement): void {
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ const alignStartPosition = {
+ x: (this.canvas.width - BoundingBox.getOriginalObject().width) / 2,
+ y: (this.canvas.height - BoundingBox.getOriginalObject().height) / 2,
+ };
+ this.ctx.drawImage(
+ path,
+ BoundingBox.getOriginalObject().x,
+ BoundingBox.getOriginalObject().y,
+ BoundingBox.getOriginalObject().width,
+ BoundingBox.getOriginalObject().height,
+ alignStartPosition.x,
+ alignStartPosition.y,
+ BoundingBox.getOriginalObject().width,
+ BoundingBox.getOriginalObject().height,
+ );
+ }
+
+ clear(): void {
+ this.canvas.width = this.canvas.height = 1;
+ this.setupCanvas();
+ }
+}
+
+export default AlignCanvas;
diff --git a/src/view/components/userQuery/PathTrackingCanvas.js b/src/view/components/userQuery/PathTrackingCanvas.js
deleted file mode 100644
index 2f0248e..0000000
--- a/src/view/components/userQuery/PathTrackingCanvas.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js";
-
-class PathTrackingCanvas {
- constructor() {
- // this.strokeCanvas = document.createElement('pathTrackingCanvas');
- this.canvas = document.getElementById('pathTrackingCanvas');
- this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
- this.setupCanvas()
- }
-
- setupCanvas() {
- this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight / 3;
-
- this.ctx.fillStyle = 'rgba(0, 0, 0)';
- this.ctx.strokeStyle = 'rgba(255, 255, 255)';
-
- this.ctx.lineWidth = CANVAS_CONFIG.lineWidth;
- this.ctx.lineCap = CANVAS_CONFIG.lineCap;
- this.ctx.lineJoin = CANVAS_CONFIG.lineJoin;
-
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- }
-
- startPath(x, y) {
- this.ctx.beginPath();
- this.ctx.moveTo(x, y);
- }
-
- drawPath(x, y) {
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
- }
-
- endPath() {
- this.ctx.closePath();
- }
-}
-
-export default PathTrackingCanvas;
\ No newline at end of file
diff --git a/src/view/components/userQuery/PathTrackingCanvas.ts b/src/view/components/userQuery/PathTrackingCanvas.ts
new file mode 100644
index 0000000..5645de8
--- /dev/null
+++ b/src/view/components/userQuery/PathTrackingCanvas.ts
@@ -0,0 +1,48 @@
+import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts';
+
+class PathTrackingCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
+ constructor() {
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ this.setupCanvas();
+ }
+
+ setupCanvas(): void {
+ // this.canvas.width = window.innerWidth;
+ // this.canvas.height = window.innerHeight * (7 / 20);
+ this.canvas.width = 1120;
+ this.canvas.height = 560;
+
+ this.ctx.fillStyle = 'rgba(0, 0, 0)';
+ this.ctx.strokeStyle = 'rgba(255, 255, 255)';
+
+ this.ctx.lineWidth = CANVAS_CONFIG.lineWidth;
+ this.ctx.lineCap = CANVAS_CONFIG.lineCap;
+ this.ctx.lineJoin = CANVAS_CONFIG.lineJoin;
+
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+
+ startPath(x: number, y: number): void {
+ this.ctx.beginPath();
+ this.ctx.moveTo(x, y);
+ }
+
+ drawPath(x: number, y: number): void {
+ this.ctx.lineTo(x, y);
+ this.ctx.stroke();
+ }
+
+ endPath(): void {
+ this.ctx.closePath();
+ }
+
+ clear(): void {
+ this.setupCanvas();
+ }
+}
+
+export default PathTrackingCanvas;
diff --git a/src/view/components/userQuery/ResizeCanvas.js b/src/view/components/userQuery/ResizeCanvas.js
deleted file mode 100644
index c424a57..0000000
--- a/src/view/components/userQuery/ResizeCanvas.js
+++ /dev/null
@@ -1,15 +0,0 @@
-class ResizeCanvas {
- constructor() {
- this.canvas = document.getElementById("resizeCanvas");
- this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
- this.canvas.width = this.canvas.height = 28;
- }
-
- downScale(path) {
- this.ctx.fillStyle = 'rgba(255,255,255)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- this.ctx.drawImage(path, 0, 0, path.width, path.height, 0, 0, this.canvas.width, this.canvas.height);
- }
-}
-
-export default ResizeCanvas;
\ No newline at end of file
diff --git a/src/view/components/userQuery/ResizeCanvas.ts b/src/view/components/userQuery/ResizeCanvas.ts
new file mode 100644
index 0000000..96a4275
--- /dev/null
+++ b/src/view/components/userQuery/ResizeCanvas.ts
@@ -0,0 +1,37 @@
+class ResizeCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
+ constructor() {
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ this.setupCanvas();
+ }
+
+ setupCanvas(): void {
+ this.canvas.width = this.canvas.height = 28;
+ this.ctx.fillStyle = 'rgba(255,255,255)';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+
+ downScale(path: HTMLCanvasElement): void {
+ this.ctx.drawImage(
+ path,
+ 0,
+ 0,
+ path.width,
+ path.height,
+ 0,
+ 0,
+ this.canvas.width,
+ this.canvas.height,
+ );
+ }
+
+ clear(): void {
+ this.setupCanvas();
+ // this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+}
+
+export default ResizeCanvas;
diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.ts
similarity index 54%
rename from src/view/components/userQuery/UserInputCanvas.js
rename to src/view/components/userQuery/UserInputCanvas.ts
index 6693753..b23d25b 100644
--- a/src/view/components/userQuery/UserInputCanvas.js
+++ b/src/view/components/userQuery/UserInputCanvas.ts
@@ -1,9 +1,21 @@
-import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js";
+import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts';
+import { userInputClipPath } from '@/view/components/userQuery/uiUtils/userInputClipPath.ts';
class UserInputCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+ private isDrawing: boolean;
+
constructor() {
- this.canvas = document.getElementById('userInputCanvas');
+ this.canvas = document.getElementById('user-input-canvas') as HTMLCanvasElement;
this.ctx = this.canvas.getContext('2d');
+
+ const pathElem = document.getElementById('arc-path');
+ pathElem.setAttribute(
+ 'd',
+ userInputClipPath({ radius: 560, canvasWidth: 1120, canvasHeight: 560 }),
+ );
+
this.isDrawing = false;
this.setupCanvas();
@@ -13,13 +25,16 @@ class UserInputCanvas {
this.ctx.lineJoin = CANVAS_CONFIG.lineJoin;
}
- clear = () => {
+ clear = (): void => {
+ this.ctx.fillStyle = 'rgba(40,40,40)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- }
+ this.drawGridDots();
+ };
- setupCanvas() {
- this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight / 3;
+ setupCanvas(): void {
+ // this.canvas.width = window.innerWidth;
+ this.canvas.width = 1120;
+ this.canvas.height = 560;
this.ctx.fillStyle = 'rgba(40,40,40)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
@@ -33,7 +48,7 @@ class UserInputCanvas {
this.ctx.fillStyle = 'rgba(255,255,255,0.3)';
this.ctx.arc(x, y, 1, 0, 2 * Math.PI);
this.ctx.fill();
- }
+ };
for (let x = 5; x < this.canvas.width; x += 12) {
for (let y = 5; y < this.canvas.height; y += 12) {
@@ -42,19 +57,19 @@ class UserInputCanvas {
}
}
- startPath(x, y) {
+ startPath(x: number, y: number): void {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
}
- drawPath(x, y) {
+ drawPath(x: number, y: number): void {
this.ctx.lineTo(x, y);
this.ctx.stroke();
}
- endPath() {
+ endPath(): void {
this.ctx.closePath();
}
}
-export default UserInputCanvas;
\ No newline at end of file
+export default UserInputCanvas;
diff --git a/src/view/components/userQuery/canvasUtils/BoundingBox.ts b/src/view/components/userQuery/canvasUtils/BoundingBox.ts
new file mode 100644
index 0000000..f99faa1
--- /dev/null
+++ b/src/view/components/userQuery/canvasUtils/BoundingBox.ts
@@ -0,0 +1,53 @@
+import { IinitialCoords, originalObject } from '../types/types.ts';
+
+class BoundingBox {
+ private readonly initialCoords: IinitialCoords;
+ private coordinate: IinitialCoords;
+ private originalObject: originalObject;
+
+ constructor() {
+ this.initialCoords = {
+ minX: Infinity,
+ minY: Infinity,
+ maxX: -Infinity,
+ maxY: -Infinity,
+ };
+ this.coordinate = { ...this.initialCoords };
+ this.originalObject = {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ };
+ }
+
+ getOriginalObject(): Readonly {
+ return { ...this.originalObject };
+ }
+
+ update(currentX: number, currentY: number): void {
+ this.coordinate = {
+ minX: Math.round(Math.min(this.coordinate.minX, currentX)),
+ minY: Math.round(Math.min(this.coordinate.minY, currentY)),
+ maxX: Math.round(Math.max(this.coordinate.maxX, currentX)),
+ maxY: Math.round(Math.max(this.coordinate.maxY, currentY)),
+ };
+ Object.assign(this.originalObject, {
+ x: this.coordinate.minX - 20,
+ y: this.coordinate.minY - 20,
+ width: this.coordinate.maxX + 40 - this.coordinate.minX,
+ height: this.coordinate.maxY + 40 - this.coordinate.minY,
+ });
+ }
+
+ log(): void {
+ console.log(this.coordinate);
+ console.log(this.originalObject);
+ }
+
+ reset(): void {
+ this.coordinate = { ...this.initialCoords };
+ }
+}
+
+export default new BoundingBox();
diff --git a/src/view/components/userQuery/types/canvas.ts b/src/view/components/userQuery/types/canvas.ts
new file mode 100644
index 0000000..7d0936a
--- /dev/null
+++ b/src/view/components/userQuery/types/canvas.ts
@@ -0,0 +1,23 @@
+export interface ICanvasBase {
+ setupCanvas(): void;
+ clear(): void;
+ canvas: HTMLCanvasElement;
+ ctx: CanvasRenderingContext2D;
+}
+
+export interface IUserInputCanvas extends IPathTrackingCanvas {
+ drawGridDots(): void;
+}
+export interface IPathTrackingCanvas {
+ startPath(x: number, y: number): void;
+ drawPath(x: number, y: number): void;
+ endPath(): void;
+}
+export interface IAlignCanvas {
+ updateCanvasScale(): void;
+ centralize(path: HTMLCanvasElement): void;
+}
+
+export interface IResizeCanvas {
+ downScale(path: HTMLCanvasElement): void;
+}
diff --git a/src/view/components/userQuery/types/types.ts b/src/view/components/userQuery/types/types.ts
new file mode 100644
index 0000000..b9e8646
--- /dev/null
+++ b/src/view/components/userQuery/types/types.ts
@@ -0,0 +1,19 @@
+export interface IinitialCoords {
+ minX: number;
+ minY: number;
+ maxX: number;
+ maxY: number;
+}
+
+export interface originalObject {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+interface BoundingBox {
+ update(currentX: number, currentY: number): void;
+ log(): void;
+ reset(): void;
+}
diff --git a/src/view/components/userQuery/uiUtils/userInputClipPath.ts b/src/view/components/userQuery/uiUtils/userInputClipPath.ts
new file mode 100644
index 0000000..eaf3d1f
--- /dev/null
+++ b/src/view/components/userQuery/uiUtils/userInputClipPath.ts
@@ -0,0 +1,27 @@
+export const userInputClipPath = ({ radius = 560, canvasWidth, canvasHeight }) => {
+ const w = canvasWidth;
+ const h = canvasHeight;
+
+ // 반지름과 호각 설정
+ const cx = w / 2;
+ const cy = radius;
+ const startAngle = Math.PI; // 180도
+ const endAngle = 2 * Math.PI; // 360도
+
+ // 반원 양 끝 좌표
+ const x1 = cx + radius * Math.cos(startAngle);
+ const y1 = cy + radius * Math.sin(startAngle);
+ const x2 = cx + radius * Math.cos(endAngle);
+ const y2 = cy + radius * Math.sin(endAngle);
+
+ // SVG arc path (절대좌표 기준)
+ const path = `
+ M ${x1} ${y1}
+ A ${radius} ${radius} 0 0 1 ${x2} ${y2}
+ L ${w} ${h}
+ L 0 ${h}
+ Z
+ `;
+
+ return path;
+};
diff --git a/src/view/styles/ViewportAdapter.ts b/src/view/styles/ViewportAdapter.ts
new file mode 100644
index 0000000..e127623
--- /dev/null
+++ b/src/view/styles/ViewportAdapter.ts
@@ -0,0 +1,68 @@
+import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts';
+const { displayNodes, displayEdges } = RENDERER_CONFIG;
+
+import DataStore from '@/controller/DataStore.ts';
+
+class ViewportAdapter {
+ private readonly root: HTMLElement;
+
+ constructor() {
+ this.root = document.documentElement;
+ this.setupRendererConfig();
+ }
+
+ getViewportSize() {
+ return {
+ width: window.innerWidth,
+ height: window.innerHeight,
+ ratio: window.innerWidth / window.innerHeight,
+ };
+ }
+
+ setupRendererConfig() {
+ const { width, height } = this.getViewportSize();
+ this.root.style.setProperty('--viewport-width', `${width}px`);
+ this.root.style.setProperty('--indicator-margin', `-15px`);
+
+ const t = width / 4 / 560;
+ const y = (1 - Math.cos(t * Math.PI)) / 2;
+ const margin = y * 560;
+
+ this.root.style.setProperty('--clear-button-y-margin', `${margin}px`);
+
+ if (width > 450) {
+ const appendedDisplayNodes = width / 50 - 9; //50px을 단위로 하나씩 노드 추가
+ DataStore.setPerceptronConfig({
+ displayNodes:
+ displayNodes + appendedDisplayNodes > 70
+ ? 70
+ : displayNodes + appendedDisplayNodes, // 성능 및 최적화, UI 고려, 렌더링 노드의 수를 70개로 제한.
+ displayEdges: displayEdges,
+ });
+
+ const indicatorHeight = 550 - (width - 450) * 0.33 + Math.pow(width / 900, 3);
+ this.root.style.setProperty(
+ '--indicator-height',
+ `${indicatorHeight < 280 ? 280 : indicatorHeight}px`,
+ ); // 화면 너비에 따라 인디케이터를 내림.(arc 모양에 따라)
+ console.log(indicatorHeight < 250 ? 250 : indicatorHeight);
+ if (width > 1060) {
+ this.root.style.setProperty(
+ '--indicator-margin',
+ `${-15 + Math.round(width - 1060) / 2}px`,
+ );
+ console.log('TEST:', Math.round(-15 + (width - 1020) / 2));
+ }
+ } else {
+ DataStore.setPerceptronConfig({
+ displayNodes: displayNodes,
+ displayEdges: displayEdges,
+ });
+ this.root.style.setProperty('--indicator-height', `550px`);
+ }
+ this.root.style.setProperty('--left-indicator-angle', `${-width / 40}deg`);
+ this.root.style.setProperty('--right-indicator-angle', `${width / 40}deg`);
+ }
+}
+
+export default ViewportAdapter;
diff --git a/src/view/styles/main.css b/src/view/styles/main.css
index b02f871..4bdf37c 100644
--- a/src/view/styles/main.css
+++ b/src/view/styles/main.css
@@ -1,3 +1,248 @@
#canvas {
border: 1px solid black;
-}
\ No newline at end of file
+}
+#app {
+ position: relative;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+}
+header {
+ display: block;
+ position: sticky;
+ top: 0;
+ left: 0;
+
+ z-index: 10;
+
+ width: 100%;
+ height: 60px;
+
+ padding: 20px 20px 20px 20px;
+ #main-title {
+ display: block;
+ font-size: 1.3rem;
+ font-weight: 800;
+ }
+ #sub-title {
+ display: block;
+ font-size: 0.65rem;
+ font-weight: 300;
+ }
+}
+
+main {
+ display: block;
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+#query-result {
+ position: relative;
+ width: 100%;
+ height: 35%;
+ /*background: #757575;*/
+
+ display: flex;
+ justify-content: center;
+ /*justify-items: flex-end;*/
+ align-items: center;
+ z-index: 5;
+ div {
+ display: block;
+
+ width: fit-content;
+ height: fit-content;
+
+ font-size: 11rem;
+ font-weight: 700;
+ font-family: "Noto Sans Syriac Western", sans-serif;
+ color: #171717;
+ }
+}
+
+#canvas-unit {
+ position: relative;
+ display: block;
+ width: 100%;
+ height: 720px;
+ /*height: 720px;*/
+ z-index: 2;
+
+ #position-aligner {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+}
+.indicator {
+ width: 50px;
+ height: 110px;
+ position: fixed;
+
+ bottom: var(--indicator-height);
+ /*bottom: 550px;*/
+ display: block;
+ z-index: 15;
+
+ backdrop-filter: blur(3px);
+ -webkit-backdrop-filter: blur(5px);
+
+}
+#left-indicator{
+ /*left: -15px;*/
+ left: var(--indicator-margin);
+ transform: rotate(var(--left-indicator-angle));
+
+ -webkit-mask-image: linear-gradient(to right, white 70%, transparent 100%);
+ mask-image: linear-gradient(to right, white 70%, transparent 100%);
+}
+#right-indicator {
+ right: var(--indicator-margin);
+ /*right: -15px;*/
+ transform: rotate(var(--right-indicator-angle));
+
+ -webkit-mask-image: linear-gradient(to left, white 70%, transparent 100%);
+ mask-image: linear-gradient(to left, white 70%, transparent 100%);
+}
+.indicator-blur {
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4x);
+ background: rgba(255, 255, 255, 0.3);
+
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+#left-arrow {
+ height: 15px;
+
+ position: absolute;
+ top: 40%;
+ right: 40%;
+ transform: translate(50%, -50%);
+ animation: left-float 1s ease-in-out infinite;
+}
+#right-arrow {
+ height: 15px;
+
+ position: absolute;
+ top: 40%;
+ left: 40%;
+ transform: translate(-50%, -50%);
+ animation: right-float 1s ease-in-out infinite;
+}
+
+
+#perceptron-section {
+ position: absolute;
+ left: 50%;
+ bottom: 0;
+
+ transform: translateX(-50%);
+ width: auto;
+ z-index: 1;
+}
+
+#user-input-section {
+ /*display: none;*/
+ position: absolute;
+ display: block;
+ left: 50%;
+ bottom: 0;
+ width: 1120px;
+ transform: translateX(-50%);
+ height: 560px;
+ z-index: 2;
+}
+
+#user-input-canvas {
+ display: block;
+ width: 100%;
+ height: 100%;
+
+ z-index: 2;
+ /* safari client 대응 */
+ transform: translateZ(0);
+ will-change: transform;
+ overflow: hidden;
+
+ clip-path: url(#arc-clip);
+ -webkit-clip-path: url(#arc-clip);
+}
+
+
+svg{
+ display: block;
+}
+
+#clear {
+ position: absolute;
+ z-index: 20;
+
+ top: 480px;
+ right: 10px;
+
+ width: 30px;
+ height: 30px;
+
+ background: none;
+ border: none;
+ img {
+ display: block;
+ width: 25px;
+
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+
+ }
+}
+
+@media (width > 900px ) {
+ #clear {
+ bottom: 280px;
+ }
+}
+@media (width <= 900px ) {
+ #clear {
+ top: calc(160px + var(--clear-button-y-margin));
+ }
+}
+@media (width >= 960px ) {
+ #clear {
+ right: calc((var(--viewport-width) - 960px + 10px) / 2);
+ }
+}
+
+
+@keyframes left-float {
+ 0% {
+ transform: translateX(1px);
+ }
+ 50% {
+ transform: translateX(-2px);
+ }
+ 100% {
+ transform: translateX(1px);
+ }
+}
+@keyframes right-float {
+ 0% {
+ transform: translateX(-1px);
+ }
+ 50% {
+ transform: translateX(1px);
+ }
+ 100% {
+ transform: translateX(-1px);
+ }
+}
+
diff --git a/src/view/styles/settings.css b/src/view/styles/settings.css
index 45f760e..210a1ab 100644
--- a/src/view/styles/settings.css
+++ b/src/view/styles/settings.css
@@ -1,5 +1,19 @@
+@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable.min.css");
+@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
+
* {
margin: 0;
padding: 0;
box-sizing: border-box;
+
+ font-family: "Pretendard Variable", Pretendard,"Roboto", sans-serif, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
+ color: #2B2B2B;
+}
+
+html {
+ font-size: 100%; /* 16px = 1rem */
+}
+
+canvas {
+ display: block;
}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..26f4f5c
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ES2022",
+ "moduleResolution": "Node",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": false,
+ "allowJs": true,
+ "checkJs": false,
+ "esModuleInterop": true,
+ "baseUrl": "./src",
+ "paths": {
+ "@/*": ["*"]
+ },
+ "types": ["node"]
+ },
+ "include": ["src/**/*"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..02b184e
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from 'vite';
+import { fileURLToPath } from 'url';
+import path from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const repo = 'NeuralNetwork-Visualization';
+const branch = process.env.GITHUB_REF_NAME || '';
+let base = `/${repo}/`;
+
+if (branch.startsWith('feat/')) {
+ base = './';
+}
+
+export default defineConfig({
+ base: base,
+ resolve: {
+ alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
+ },
+ publicDir: 'public',
+ build: {
+ outDir: 'dist',
+ target: 'esnext',
+ },
+ server: {
+ port: 5173,
+ open: true,
+ },
+});