From 5d69739d0dbe0701378fedf046f7b4004ef68e7e Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:34:11 +0900 Subject: [PATCH 01/94] chore: add eslint, prettier (code formating) --- .eslintrc.json | 20 ++++++++++++ .idea/codeStyles/Project.xml | 48 ++++++++++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 +++ .idea/prettier.xml | 8 +++++ .prettierrc | 7 ++++ 5 files changed, 88 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/prettier.xml create mode 100644 .prettierrc diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..1fe0a72 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], + "plugins": ["prettier"], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "prettier/prettier": "error", + "no-unused-vars": "warn", + "no-console": "off" + } +} diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..904225f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 0000000..ea0f3cf --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bae74ff --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "trailingComma": "all", + "singleQuote": true, + "semi": true +} \ No newline at end of file From b30c92d4e50b2ec23e702ad5cd264982bfa5aac4 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:40:56 +0900 Subject: [PATCH 02/94] build: add vite --- vite.config.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 vite.config.ts diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..58a9b50 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: '.', // 기본값, 생략 가능 + publicDir: 'public', + build: { + outDir: 'dist', + target: 'esnext', + }, + server: { + port: 5173, + open: true, + }, +}); From 1ddc1ce9658d50dc946505a947d92d0e399d3e2e Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:42:54 +0900 Subject: [PATCH 03/94] chore: add pnpm --- package.json | 20 +- pnpm-lock.yaml | 752 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 768 insertions(+), 4 deletions(-) create mode 100644 pnpm-lock.yaml diff --git a/package.json b/package.json index 15fecff..d780ffc 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,21 @@ "version": "1.0.0", "type": "module", "description": "", - "main": "public/index.html", + "main": "index.html", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write ." }, - "private": true -} + "private": true, + "devDependencies": { + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.3", + "prettier": "3.6.2" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..7ec903a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,752 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + eslint: + specifier: ^9.32.0 + version: 9.32.0 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.32.0) + eslint-plugin-prettier: + specifier: ^5.5.3 + version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2) + prettier: + specifier: 3.6.2 + version: 3.6.2 + +packages: + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.32.0': + resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.3: + resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.32.0: + resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': + dependencies: + eslint: 9.32.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.0': {} + + '@eslint/core@0.15.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.32.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.4': + dependencies: + '@eslint/core': 0.15.1 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@pkgr/core@0.2.9': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.32.0): + dependencies: + eslint: 9.32.0 + + eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2): + dependencies: + eslint: 9.32.0 + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.32.0) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.32.0: + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.15.1 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.32.0 + '@eslint/plugin-kit': 0.3.4 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + punycode@2.3.1: {} + + resolve-from@4.0.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} From a3b6bd7442e022d2ee465471b339c3c87b92edb4 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:45:42 +0900 Subject: [PATCH 04/94] chore: add tsconfig --- tsconfig.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tsconfig.json diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0a0b2a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2022", + "moduleResolution": "Node", + "outDir": "./dist", + "rootDir": "./src", + "strict": false, + "allowJs": true, + "checkJs": false, + "esModuleInterop": true + }, + "include": ["src/**/*"] +} From a27252ac692e60961d5acc2386e642ae82f368af Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:46:04 +0900 Subject: [PATCH 05/94] chor: add path on jsconfig --- jsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/jsconfig.json b/jsconfig.json index 8ecfcad..ec6c9e9 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -3,6 +3,7 @@ "baseUrl": "src", "paths": { "@controller/*": ["controller/*"], + "@core/*": ["core/*"], "@view/*": ["view/*"] } }, From a9f0797fa0d3629cc261187e80a81ab3153395e1 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:46:13 +0900 Subject: [PATCH 06/94] chore: add gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 06362ca..3d2fec6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ dev/train/trainData/mnist/mnistTraindata_large.js dev/train/trainData/mnist/mnist_train.csv +node_modules +dist +build +*.min.js \ No newline at end of file From 777e6b7fe830fcc1fcbfdf2fc4ed90a71aa63cd9 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:48:01 +0900 Subject: [PATCH 07/94] =?UTF-8?q?refactor:=20vite=EC=9D=98=20root=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9D=84=20=EC=A4=80=EC=88=98=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=B4=20index.html=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html => index.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename public/index.html => index.html (86%) diff --git a/public/index.html b/index.html similarity index 86% rename from public/index.html rename to index.html index 7df7fa3..7f8501c 100644 --- a/public/index.html +++ b/index.html @@ -6,9 +6,8 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> Neural Network Visualization - - - + + From 75a022448bf52ad3638dea60a278ae6e36ceff36 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:51:26 +0900 Subject: [PATCH 08/94] =?UTF-8?q?migratoin:=20`QueryProcessController`,=20?= =?UTF-8?q?`throttle`,=20`NueralNetworkBase`=EB=A5=BC=20TS=EB=A1=9C=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev/train/Trainer.js | 2 +- src/controller/AppContoller.js | 30 ++++---- .../queryPipeline/QueryProcessController.js | 48 ------------- .../queryPipeline/QueryProcessController.ts | 68 +++++++++++++++++++ .../{throttle.js => throttle.ts} | 6 +- .../types/QueryProcessController.ts | 16 +++++ src/controller/types/weights.ts | 4 ++ src/core/NeuralNetworkBase.js | 35 ---------- src/core/NeuralNetworkBase.ts | 41 +++++++++++ src/core/types/NeuralNetworkBase.ts | 4 ++ 10 files changed, 154 insertions(+), 100 deletions(-) delete mode 100644 src/controller/queryPipeline/QueryProcessController.js create mode 100644 src/controller/queryPipeline/QueryProcessController.ts rename src/controller/queryPipeline/{throttle.js => throttle.ts} (51%) create mode 100644 src/controller/queryPipeline/types/QueryProcessController.ts create mode 100644 src/controller/types/weights.ts delete mode 100644 src/core/NeuralNetworkBase.js create mode 100644 src/core/NeuralNetworkBase.ts create mode 100644 src/core/types/NeuralNetworkBase.ts diff --git a/dev/train/Trainer.js b/dev/train/Trainer.js index 3594d8f..4307120 100644 --- a/dev/train/Trainer.js +++ b/dev/train/Trainer.js @@ -1,4 +1,4 @@ -import { NeuralNetworkBase } from "../../src/core/NeuralNetworkBase.js"; +import { NeuralNetworkBase } from "../../src/core/NeuralNetworkBase.ts"; // matrix operations import {matrixMultiply, transposeMatrix} from "../../src/core/ops/matrixOps.js"; diff --git a/src/controller/AppContoller.js b/src/controller/AppContoller.js index 22fd9c7..9d2f86b 100644 --- a/src/controller/AppContoller.js +++ b/src/controller/AppContoller.js @@ -1,15 +1,15 @@ -import WeightManager from "../core/WeightManager.js"; -import NeuralNetworkBase from "../core/NeuralNetworkBase.js"; -import QueryProcessController from "./queryPipeline/QueryProcessController.js"; -import UserInputCanvas from "../view/components/userQuery/UserInputCanvas.js"; -import PathTrackingCanvas from "../view/components/userQuery/PathTrackingCanvas.js"; -import AlignCanvas from "../view/components/userQuery/AlignCanvas.js"; -import ResizeCanvas from "../view/components/userQuery/ResizeCanvas.js"; -import { DataStore } from "./DataStore.js"; +import WeightManager from '../core/WeightManager.js'; +import NeuralNetworkBase from '../core/NeuralNetworkBase.ts'; +import QueryProcessController from './queryPipeline/QueryProcessController.ts'; +import UserInputCanvas from '../view/components/userQuery/UserInputCanvas.js'; +import PathTrackingCanvas from '../view/components/userQuery/PathTrackingCanvas.js'; +import AlignCanvas from '../view/components/userQuery/AlignCanvas.js'; +import ResizeCanvas from '../view/components/userQuery/ResizeCanvas.js'; +import { DataStore } from './DataStore.js'; -import eventBus from "./EventBus.js"; -import { DATA_EVENTS, HANDLER_EVENTS } from "./constants/events.js"; -import { NETWORK_CONFIG } from "./constants/networkConfig.js"; +import eventBus from './EventBus.js'; +import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.js'; +import { NETWORK_CONFIG } from './constants/networkConfig.ts'; class AppController { constructor({ dataStore }) { @@ -28,8 +28,10 @@ class AppController { $NN: $NN, }); eventBus.emit(HANDLER_EVENTS.APP_READY, this.dataStore); - eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => {console.log("RESULT CHANGED", data)}); - }; + eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => { + console.log('RESULT CHANGED', data); + }); + } } -export default AppController; \ No newline at end of file +export default AppController; diff --git a/src/controller/queryPipeline/QueryProcessController.js b/src/controller/queryPipeline/QueryProcessController.js deleted file mode 100644 index 0bc3697..0000000 --- a/src/controller/queryPipeline/QueryProcessController.js +++ /dev/null @@ -1,48 +0,0 @@ -import eventBus from "../EventBus.js"; -import { DATA_EVENTS, HANDLER_EVENTS, DRAWING_EVENTS } from "../constants/events.js"; -import DrawingEventHandler from "./DrawingEventHandler.js"; -import {pixelExtractor} from "../queryPipeline/pixelExtractor.js"; -import {throttle} from "../queryPipeline/throttle.js"; - - -class QueryProcessController { - constructor({ userInputCanvas, trackingCanvas, alignCanvas, resizeCanvas, $NN }) { - const drawingEventHandler = new DrawingEventHandler(); - this.userInputCanvas = userInputCanvas; - this.trackingCanvas = trackingCanvas; - this.alignCanvas = alignCanvas; - this.resizeCanvas = resizeCanvas; - - this.$NN = $NN; - this.drawingEvent(); - } - - drawingEvent() { - const throttleQuery = throttle((inputs) => { - const result = this.$NN.query(inputs); - if(result) { - eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); - // Array 중 가장 값이 큰 값의 index number가 추론 결과 - } - },100); - - eventBus.on(DRAWING_EVENTS.START_DRAW, ({x, y}) => { - this.userInputCanvas.startPath(x, y); - this.trackingCanvas.startPath(x, y); - }); - eventBus.on(DRAWING_EVENTS.DRAW, ({x, y}) => { - this.userInputCanvas.drawPath(x, y); - this.trackingCanvas.drawPath(x, y); - this.alignCanvas.updateCanvasScale(); - this.alignCanvas.centralize(this.trackingCanvas.canvas); - this.resizeCanvas.downScale(this.alignCanvas.canvas); - throttleQuery(pixelExtractor(this.resizeCanvas)); - }); - eventBus.on(DRAWING_EVENTS.END_DRAW, () => { - this.userInputCanvas.endPath(); - this.trackingCanvas.endPath(); - }); - } -} - -export default QueryProcessController; \ No newline at end of file diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts new file mode 100644 index 0000000..a1b0cd4 --- /dev/null +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -0,0 +1,68 @@ +import eventBus from '../EventBus.js'; +import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js'; +import DrawingEventHandler from './DrawingEventHandler.js'; +import { pixelExtractor } from './pixelExtractor.js'; +import { throttle } from './throttle'; + +import { IQueryProcessControllerProps } from './types/QueryProcessController.js'; +import { + ICanvasBase, + IUserInputCanvas, + IPathTrackingCanvas, + IAlignCanvas, + IResizeCanvas, +} from '../../view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '../../core/types/NeuralNetworkBase'; + +class QueryProcessController { + private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; + private readonly trackingCanvas: ICanvasBase & IPathTrackingCanvas; + private readonly alignCanvas: ICanvasBase & IAlignCanvas; + private readonly resizeCanvas: ICanvasBase & IResizeCanvas; + private readonly $NN: INeuralNetworkBase; + + constructor({ + userInputCanvas, + trackingCanvas, + alignCanvas, + resizeCanvas, + $NN, + }: IQueryProcessControllerProps) { + new DrawingEventHandler(); + this.userInputCanvas = userInputCanvas; + this.trackingCanvas = trackingCanvas; + this.alignCanvas = alignCanvas; + this.resizeCanvas = resizeCanvas; + + this.$NN = $NN; + this.drawingEvent(); + } + + drawingEvent(): void { + const throttleQuery = throttle((inputs) => { + const result: number[] = this.$NN.query(inputs); + if (result) { + eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); + } + }, 100); + + eventBus.on(DRAWING_EVENTS.START_DRAW, ({ x, y }) => { + this.userInputCanvas.startPath(x, y); + this.trackingCanvas.startPath(x, y); + }); + eventBus.on(DRAWING_EVENTS.DRAW, ({ x, y }) => { + this.userInputCanvas.drawPath(x, y); + this.trackingCanvas.drawPath(x, y); + this.alignCanvas.updateCanvasScale(); + this.alignCanvas.centralize(this.trackingCanvas.canvas); + this.resizeCanvas.downScale(this.alignCanvas.canvas); + throttleQuery(pixelExtractor(this.resizeCanvas)); + }); + eventBus.on(DRAWING_EVENTS.END_DRAW, () => { + this.userInputCanvas.endPath(); + this.trackingCanvas.endPath(); + }); + } +} + +export default QueryProcessController; diff --git a/src/controller/queryPipeline/throttle.js b/src/controller/queryPipeline/throttle.ts similarity index 51% rename from src/controller/queryPipeline/throttle.js rename to src/controller/queryPipeline/throttle.ts index 8d25156..bc966df 100644 --- a/src/controller/queryPipeline/throttle.js +++ b/src/controller/queryPipeline/throttle.ts @@ -1,6 +1,8 @@ -export const throttle = (fn, delay) => { +export const throttle = any> ( + fn: T, delay: number +): (...args: Parameters) => ReturnType => { let pause = false; - return (...args) => { + return (...args: Parameters): ReturnType => { if(!pause) { const result = fn(...args); pause = true; diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts new file mode 100644 index 0000000..631d61b --- /dev/null +++ b/src/controller/queryPipeline/types/QueryProcessController.ts @@ -0,0 +1,16 @@ +import { + ICanvasBase, + IUserInputCanvas, + IPathTrackingCanvas, + IAlignCanvas, + IResizeCanvas, +} from '../../../view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '../../../core/types/NeuralNetworkBase'; + +export interface IQueryProcessControllerProps { + userInputCanvas: ICanvasBase & IUserInputCanvas; + trackingCanvas: ICanvasBase & IPathTrackingCanvas; + alignCanvas: ICanvasBase & IAlignCanvas; + resizeCanvas: ICanvasBase & IResizeCanvas; + $NN: INeuralNetworkBase; +} diff --git a/src/controller/types/weights.ts b/src/controller/types/weights.ts new file mode 100644 index 0000000..a149746 --- /dev/null +++ b/src/controller/types/weights.ts @@ -0,0 +1,4 @@ +export interface weights { + W_inputToHidden: number[][]; + W_hiddenToOutput: number[][]; +} diff --git a/src/core/NeuralNetworkBase.js b/src/core/NeuralNetworkBase.js deleted file mode 100644 index adb4bfa..0000000 --- a/src/core/NeuralNetworkBase.js +++ /dev/null @@ -1,35 +0,0 @@ -import activationFunction from "./ops/activationOps.js"; -import { matrixMultiply } from "./ops/matrixOps.js"; -import eventBus from "../controller/EventBus.js"; -import { DATA_EVENTS } from "../controller/constants/events.js"; -import { NETWORK_CONFIG } from "../controller/constants/networkConfig.js"; - -class NeuralNetworkBase { - constructor(weights) { - this.inputNodes = NETWORK_CONFIG.inputNodes; - this.hiddenNodes = NETWORK_CONFIG.hiddenNodes; - this.outputNodes = NETWORK_CONFIG.outputNodes; - this.learningRate = NETWORK_CONFIG.learningRate; - - this.W_inputToHidden = weights?.W_inputToHidden ?? null; - this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null; - } - - // CNN operations - feedForward(inputs) { - console.log("inputs", inputs); - const hiddenInputs = matrixMultiply(this.W_inputToHidden, inputs.map(v => [v])); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환. - const hiddenOutputs = activationFunction(hiddenInputs); - const finalInputs = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); - const finalOutputs = activationFunction(finalInputs); - return { hiddenInputs, hiddenOutputs, finalInputs, finalOutputs }; - } - - query(inputs){ - const { hiddenInputs, hiddenOutputs , finalOutputs } = this.feedForward(inputs); - eventBus.emit(DATA_EVENTS.NODE_UPDATE, {hiddenInputs, hiddenOutputs, finalOutputs}); - return finalOutputs; - } -} - -export default NeuralNetworkBase; \ No newline at end of file diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts new file mode 100644 index 0000000..152ee46 --- /dev/null +++ b/src/core/NeuralNetworkBase.ts @@ -0,0 +1,41 @@ +import activationFunction from './ops/activationOps.js'; +import { matrixMultiply } from './ops/matrixOps.js'; +import eventBus from '../controller/EventBus.js'; +import { DATA_EVENTS } from '../controller/constants/events.js'; + +import { weights } from '../controller/types/weights'; + +class NeuralNetworkBase { + private readonly W_inputToHidden: number[][] | null; + private readonly W_hiddenToOutput: number[][] | null; + + constructor(weights: Partial) { + // weight를 optional하게 처리하기 위해 Partial 사용 + this.W_inputToHidden = weights?.W_inputToHidden ?? null; + this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null; + } + + // CNN operations + feedForward(inputs: number[]): { + hiddenInputs: number[]; + hiddenOutputs: number[]; + finalOutputs: number[]; + } { + const hiddenInputs: number[] = matrixMultiply( + this.W_inputToHidden, + inputs.map((v) => [v]), + ); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환. + const hiddenOutputs: number[] = activationFunction(hiddenInputs); + const finalInputs: number[] = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); + const finalOutputs: number[] = activationFunction(finalInputs); + return { hiddenInputs, hiddenOutputs, finalOutputs }; + } + + query(inputs: number[]): number[] { + const { hiddenInputs, hiddenOutputs, finalOutputs } = this.feedForward(inputs); + eventBus.emit(DATA_EVENTS.NODE_UPDATE, { hiddenInputs, hiddenOutputs, finalOutputs }); + return finalOutputs; + } +} + +export default NeuralNetworkBase; diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts new file mode 100644 index 0000000..7035612 --- /dev/null +++ b/src/core/types/NeuralNetworkBase.ts @@ -0,0 +1,4 @@ +export interface INeuralNetworkBase { + feedForward(inputs: number[]): { hiddenInputs: number[]; hiddenOutputs: number[] }; + query(inputs: number[]): number[]; +} From 24f10ead8ba3766e1d70dcff5f99e8095e4721eb Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:53:53 +0900 Subject: [PATCH 09/94] =?UTF-8?q?chore:=20=EC=83=81=EC=88=98=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BA=94=EB=B2=84=EC=8A=A4=EC=9D=98=20type=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/networkConfig.js | 6 ----- src/controller/constants/networkConfig.ts | 8 +++++++ src/controller/types/constant.ts | 6 +++++ src/view/components/userQuery/types/canvas.ts | 23 +++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) delete mode 100644 src/controller/constants/networkConfig.js create mode 100644 src/controller/constants/networkConfig.ts create mode 100644 src/controller/types/constant.ts create mode 100644 src/view/components/userQuery/types/canvas.ts diff --git a/src/controller/constants/networkConfig.js b/src/controller/constants/networkConfig.js deleted file mode 100644 index 8d6a6c8..0000000 --- a/src/controller/constants/networkConfig.js +++ /dev/null @@ -1,6 +0,0 @@ -export const NETWORK_CONFIG = { - inputNodes: 784, - hiddenNodes: 200, - outputNodes: 10, - learningRate: 0.15, -} \ No newline at end of file diff --git a/src/controller/constants/networkConfig.ts b/src/controller/constants/networkConfig.ts new file mode 100644 index 0000000..ed551e8 --- /dev/null +++ b/src/controller/constants/networkConfig.ts @@ -0,0 +1,8 @@ +import { CONSTANT } from '../types/constant'; + +export const NETWORK_CONFIG: CONSTANT = { + inputNodes: 784, + hiddenNodes: 200, + outputNodes: 10, + learningRate: 0.15, +}; diff --git a/src/controller/types/constant.ts b/src/controller/types/constant.ts new file mode 100644 index 0000000..f4c95e9 --- /dev/null +++ b/src/controller/types/constant.ts @@ -0,0 +1,6 @@ +export interface CONSTANT { + inputNodes: number; + hiddenNodes: number; + outputNodes: number; + learningRate: number; +} diff --git a/src/view/components/userQuery/types/canvas.ts b/src/view/components/userQuery/types/canvas.ts new file mode 100644 index 0000000..446c51c --- /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; +} \ No newline at end of file From cbdde7d82d31134816e0a1a0ed3e32428e4dc57a Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:54:26 +0900 Subject: [PATCH 10/94] =?UTF-8?q?rename:=20vite=EC=9D=98=20entry=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{index.js => main.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{index.js => main.ts} (100%) diff --git a/src/index.js b/src/main.ts similarity index 100% rename from src/index.js rename to src/main.ts From c1bfc4f17c40949dc8c7beda0d8b479f59190858 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 27 Jul 2025 15:58:58 +0900 Subject: [PATCH 11/94] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85=20=EB=B0=8F=20interface=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=ED=99=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20clear=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80(canvas)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 faet/query에 추가해야할 커밋이었으나, 빌드 및 패키지 매니저의 추가로 환경 설정이 불편하여 해당 브랜치에 그대로 추가. (브랜치 스위칭하는데 더 큰 비용이 들 것 같음) --- src/view/components/CanvasComponentBase.js | 3 +-- src/view/components/userQuery/AlignCanvas.js | 5 +++++ src/view/components/userQuery/ResizeCanvas.js | 4 ++++ src/view/components/userQuery/UserInputCanvas.js | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) 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/userQuery/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.js index c5ae35e..743ecdc 100644 --- a/src/view/components/userQuery/AlignCanvas.js +++ b/src/view/components/userQuery/AlignCanvas.js @@ -38,6 +38,11 @@ class AlignCanvas { BoundingBox.originalObject.width, BoundingBox.originalObject.height ); } + + clear() { + this.canvas.width = this.canvas.height = 1; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } } export default AlignCanvas; \ No newline at end of file diff --git a/src/view/components/userQuery/ResizeCanvas.js b/src/view/components/userQuery/ResizeCanvas.js index c424a57..cd0a657 100644 --- a/src/view/components/userQuery/ResizeCanvas.js +++ b/src/view/components/userQuery/ResizeCanvas.js @@ -10,6 +10,10 @@ class ResizeCanvas { 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); } + + clear() { + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + } } export default ResizeCanvas; \ No newline at end of file diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.js index 6693753..b329245 100644 --- a/src/view/components/userQuery/UserInputCanvas.js +++ b/src/view/components/userQuery/UserInputCanvas.js @@ -15,6 +15,7 @@ class UserInputCanvas { clear = () => { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.drawGridDots(); } setupCanvas() { From ccab05025cee0bd735c28d3edd64a49bc15c800a Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 22:16:40 +0900 Subject: [PATCH 12/94] refactor: migration to typescript(WIP) --- dev/train/Trainer.js | 43 +++++++++++----- src/controller/AppContoller.js | 13 ++--- src/controller/DataStore.ts | 50 +++++++++++++++++++ src/controller/EventBus.js | 20 -------- src/controller/EventBus.ts | 33 ++++++++++++ .../{canvasConfig.js => canvasConfig.ts} | 4 +- .../constants/{events.js => events.ts} | 18 ++++--- src/controller/constants/networkConfig.ts | 4 +- src/controller/constants/types/events.ts | 0 .../types/networkConfig.ts} | 2 +- src/controller/dataStore.js | 42 ---------------- .../queryPipeline/DrawingEventHandler.js | 39 ++++++++------- .../queryPipeline/QueryProcessController.ts | 25 ++++++---- .../queryPipeline/pixelExtractor.js | 28 ----------- .../queryPipeline/pixelExtractor.ts | 42 ++++++++++++++++ .../types/DrawingEventHandler.ts | 12 +++++ .../types/QueryProcessController.ts | 2 + src/controller/types/DataStore.ts | 16 ++++++ src/controller/types/eventBus.ts | 31 ++++++++++++ src/core/NeuralNetworkBase.ts | 17 ++++--- src/core/WeightManager.js | 33 ------------ src/core/WeightManager.ts | 43 ++++++++++++++++ src/core/ops/matrixOps.js | 17 ------- src/core/ops/matrixOps.ts | 25 ++++++++++ src/core/ops/types/OpsType.ts | 1 + src/core/utils/createRandomWeights.js | 7 --- src/core/utils/createRandomWeights.ts | 7 +++ src/core/utils/loadPretrainedWeights.js | 5 -- src/core/utils/loadPretrainedWeights.ts | 7 +++ src/main.ts | 5 +- src/view/DataPresnter.ts | 0 .../components/canvasUtils/BoundingBox.js | 38 -------------- .../components/canvasUtils/BoundingBox.ts | 44 ++++++++++++++++ src/view/components/canvasUtils/types.ts | 19 +++++++ src/view/components/userQuery/AlignCanvas.js | 33 ++++++------ .../userQuery/PathTrackingCanvas.js | 8 +-- .../components/userQuery/UserInputCanvas.js | 8 +-- 37 files changed, 458 insertions(+), 283 deletions(-) create mode 100644 src/controller/DataStore.ts delete mode 100644 src/controller/EventBus.js create mode 100644 src/controller/EventBus.ts rename src/controller/constants/{canvasConfig.js => canvasConfig.ts} (53%) rename src/controller/constants/{events.js => events.ts} (54%) create mode 100644 src/controller/constants/types/events.ts rename src/controller/{types/constant.ts => constants/types/networkConfig.ts} (75%) delete mode 100644 src/controller/dataStore.js delete mode 100644 src/controller/queryPipeline/pixelExtractor.js create mode 100644 src/controller/queryPipeline/pixelExtractor.ts create mode 100644 src/controller/queryPipeline/types/DrawingEventHandler.ts create mode 100644 src/controller/types/DataStore.ts create mode 100644 src/controller/types/eventBus.ts delete mode 100644 src/core/WeightManager.js create mode 100644 src/core/WeightManager.ts delete mode 100644 src/core/ops/matrixOps.js create mode 100644 src/core/ops/matrixOps.ts create mode 100644 src/core/ops/types/OpsType.ts delete mode 100644 src/core/utils/createRandomWeights.js create mode 100644 src/core/utils/createRandomWeights.ts delete mode 100644 src/core/utils/loadPretrainedWeights.js create mode 100644 src/core/utils/loadPretrainedWeights.ts create mode 100644 src/view/DataPresnter.ts delete mode 100644 src/view/components/canvasUtils/BoundingBox.js create mode 100644 src/view/components/canvasUtils/BoundingBox.ts create mode 100644 src/view/components/canvasUtils/types.ts diff --git a/dev/train/Trainer.js b/dev/train/Trainer.js index 4307120..0434c50 100644 --- a/dev/train/Trainer.js +++ b/dev/train/Trainer.js @@ -1,27 +1,44 @@ -import { NeuralNetworkBase } from "../../src/core/NeuralNetworkBase.ts"; +import { NeuralNetworkBase } from '../../src/core/NeuralNetworkBase.ts'; // matrix operations -import {matrixMultiply, transposeMatrix} from "../../src/core/ops/matrixOps.js"; +import { matrixMultiply, transposeMatrix } from '../../src/core/ops/matrixOps.ts'; export class Trainer extends NeuralNetworkBase { - train(inputs, targets){ + train(inputs, targets) { // CNN operations const { finalOutputs, hiddenOutputs } = this.feedForward(inputs); // calculate errors - const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map(v => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정 + const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map((v) => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정 const hidden_errors = matrixMultiply(transposeMatrix(this.W_hiddenToOutput), output_errors); // 은닉 계층의 오차를 은닉-> 출력 계층의 가중치값과(W_hiddenToOutput.T) 출력 계층의 오차들을 재조합하여 계산 // activation function derivative - const activationDerivative_HtO = finalOutputs.map(v => 1.0 - v).map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative - const outputGradient = activationDerivative_HtO.map((v, idx) => v * output_errors[idx]).map(v => [v]); - const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map(array => array.map(v => v * this.learningRate)); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트 + const activationDerivative_HtO = finalOutputs + .map((v) => 1.0 - v) + .map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative + const outputGradient = activationDerivative_HtO + .map((v, idx) => v * output_errors[idx]) + .map((v) => [v]); + const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map( + (array) => array.map((v) => v * this.learningRate), + ); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트 - const activationDerivative_ItH = hiddenOutputs.map(v => 1.0 - v).map((v, idx) => v * hiddenOutputs[idx]); - const hiddenGradient = activationDerivative_ItH.map((v, idx) => v * hidden_errors[idx]).map(v => [v]); - const W_ItH_update = matrixMultiply(hiddenGradient, transposeMatrix(inputs.map(v => [v]))).map(array => array.map(v => v * this.learningRate)); + const activationDerivative_ItH = hiddenOutputs + .map((v) => 1.0 - v) + .map((v, idx) => v * hiddenOutputs[idx]); + const hiddenGradient = activationDerivative_ItH + .map((v, idx) => v * hidden_errors[idx]) + .map((v) => [v]); + const W_ItH_update = matrixMultiply( + hiddenGradient, + transposeMatrix(inputs.map((v) => [v])), + ).map((array) => array.map((v) => v * this.learningRate)); // update weights - this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i)=> row.map((v, j) => v + W_HtO_Update[i][j])); - this.W_inputToHidden = this.W_inputToHidden.map((row, i)=> row.map((v, j) => v + W_ItH_update[i][j])); + this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i) => + row.map((v, j) => v + W_HtO_Update[i][j]), + ); + this.W_inputToHidden = this.W_inputToHidden.map((row, i) => + row.map((v, j) => v + W_ItH_update[i][j]), + ); } -} \ No newline at end of file +} diff --git a/src/controller/AppContoller.js b/src/controller/AppContoller.js index 9d2f86b..eca5910 100644 --- a/src/controller/AppContoller.js +++ b/src/controller/AppContoller.js @@ -1,21 +1,17 @@ -import WeightManager from '../core/WeightManager.js'; +import WeightManager from '../core/WeightManager.ts'; import NeuralNetworkBase from '../core/NeuralNetworkBase.ts'; import QueryProcessController from './queryPipeline/QueryProcessController.ts'; import UserInputCanvas from '../view/components/userQuery/UserInputCanvas.js'; import PathTrackingCanvas from '../view/components/userQuery/PathTrackingCanvas.js'; import AlignCanvas from '../view/components/userQuery/AlignCanvas.js'; import ResizeCanvas from '../view/components/userQuery/ResizeCanvas.js'; -import { DataStore } from './DataStore.js'; +import DataStore from './DataStore.ts'; -import eventBus from './EventBus.js'; -import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.js'; +import eventBus from './EventBus.ts'; +import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; class AppController { - constructor({ dataStore }) { - this.dataStore = dataStore; - } - async initialize() { const $WM = new WeightManager(NETWORK_CONFIG); const $NN = new NeuralNetworkBase(await $WM.getWeights()); @@ -26,6 +22,7 @@ class AppController { alignCanvas: new AlignCanvas(), resizeCanvas: new ResizeCanvas(), $NN: $NN, + $DS: $DS, }); eventBus.emit(HANDLER_EVENTS.APP_READY, this.dataStore); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => { diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts new file mode 100644 index 0000000..eecdd50 --- /dev/null +++ b/src/controller/DataStore.ts @@ -0,0 +1,50 @@ +import eventBus from './EventBus.js'; +import { DATA_EVENTS } from './constants/events.js'; + +import { nodeState } from './types/DataStore'; + +class DataStore { + private queryInfo: number[] | null = null; + private nodeState: nodeState | null = null; + private queryResult: number[] | null = null; + + constructor() { + this.queryInfo = null; + this.nodeState = null; + this.queryResult = null; + } + + setQueryInfo(queryInfo: number[]): void { + if (queryInfo === null) throw new Error('No query info found'); + this.queryInfo = queryInfo; + eventBus.emit(DATA_EVENTS.QUERY_CHANGED, this.queryInfo); + } + getQueryInfo(): number[] { + return this.queryInfo; + } + + setNodeState(nodeState: nodeState): void { + if (nodeState === null) throw new Error('No node state found'); + this.nodeState = nodeState; + } + getNodeState(): nodeState { + return this.nodeState; + } + + setQueryResult(queryResult: number[]): void { + if (queryResult === null) throw new Error('No query result found'); + this.queryResult = queryResult; + eventBus.emit(DATA_EVENTS.RESULT_CHANGED, queryResult); + } + getQueryResult() { + return this.queryResult; + } + + reset() { + this.queryInfo = null; + this.nodeState = null; + this.queryResult = null; + } +} + +export default DataStore; diff --git a/src/controller/EventBus.js b/src/controller/EventBus.js deleted file mode 100644 index e8d182a..0000000 --- a/src/controller/EventBus.js +++ /dev/null @@ -1,20 +0,0 @@ -const EventBus = { - _events: {}, - - on(eventName, subscriber) { - if(!this._events[eventName]) this._events[eventName] = []; - this._events[eventName].push(subscriber); - }, - - off(eventName, subscriber) { - if(!this._events[eventName]) return; - this._events[eventName] = this._events[eventName].filter(fn => fn !== subscriber); - }, - - emit(eventName, payload) { - if(!this._events[eventName]) return; - this._events[eventName].forEach(fn => fn(payload)); - } -} - -export default EventBus; \ No newline at end of file diff --git a/src/controller/EventBus.ts b/src/controller/EventBus.ts new file mode 100644 index 0000000..ffe271f --- /dev/null +++ b/src/controller/EventBus.ts @@ -0,0 +1,33 @@ +import { IEventBus } from './types/eventBus'; +import { eventPayloads } from './types/eventBus'; + +const EventBus: IEventBus = { + events: {}, + + on( + eventName: K, + subscriber: (payload: eventPayloads[K]) => any, + ): void { + if (!this.events[eventName]) this.events[eventName] = []; + this.events[eventName].push(subscriber); + }, + + off( + eventName: K, + subscriber: (payload: eventPayloads[K]) => any, + ): void { + if (!this.events[eventName]) return; + this.events[eventName] = this.events[eventName].filter( + (fn: (payload: eventPayloads[K]) => void): boolean => fn !== subscriber, + ); + }, + + emit(eventName: K, payload: eventPayloads[K]): void { + if (!this.events[eventName]) return; + this.events[eventName].forEach((fn: (payload: eventPayloads[K]) => any): any => + fn(payload), + ); + }, +}; + +export default EventBus; diff --git a/src/controller/constants/canvasConfig.js b/src/controller/constants/canvasConfig.ts similarity index 53% rename from src/controller/constants/canvasConfig.js rename to src/controller/constants/canvasConfig.ts index 23eef1c..dbca9a0 100644 --- a/src/controller/constants/canvasConfig.js +++ b/src/controller/constants/canvasConfig.ts @@ -2,4 +2,6 @@ export const CANVAS_CONFIG = Object.freeze({ lineWidth: 20, lineCap: 'round', lineJoin: 'round', -}); \ No newline at end of file +} as const); + +export type CanvasConfig = (typeof CANVAS_CONFIG)[keyof typeof CANVAS_CONFIG]; diff --git a/src/controller/constants/events.js b/src/controller/constants/events.ts similarity index 54% rename from src/controller/constants/events.js rename to src/controller/constants/events.ts index edebe68..7c6ad31 100644 --- a/src/controller/constants/events.js +++ b/src/controller/constants/events.ts @@ -4,18 +4,22 @@ export const DATA_EVENTS = Object.freeze({ NODE_UPDATE: 'node:update', NODE_CHANGED: 'node:changed', RESULT_UPDATE: 'result:update', - RESULT_CHANGED: 'result:changed' -}); + RESULT_CHANGED: 'result:changed', +} as const); export const HANDLER_EVENTS = Object.freeze({ APP_READY: 'app:ready', NN_INITIALIZE: 'nn:initialize', ICH_INITIALIZE: 'ich:initialize', -}) +} as const); export const DRAWING_EVENTS = Object.freeze({ - START_DRAW: 'draw: start', - DRAW: 'draw: drawing', - END_DRAW: 'draw: end', + START_DRAW: 'draw:start', + DRAW: 'draw:drawing', + END_DRAW: 'draw:end', BOUNDINGBOX_UPDATE: 'boundingbox:update', -}) \ No newline at end of file +} as const); + +export type DataEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; +export type HandlerEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; +export type DrawingEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS]; diff --git a/src/controller/constants/networkConfig.ts b/src/controller/constants/networkConfig.ts index ed551e8..fe022fd 100644 --- a/src/controller/constants/networkConfig.ts +++ b/src/controller/constants/networkConfig.ts @@ -1,6 +1,6 @@ -import { CONSTANT } from '../types/constant'; +import { networkConfig } from './types/networkConfig'; -export const NETWORK_CONFIG: CONSTANT = { +export const NETWORK_CONFIG: networkConfig = { inputNodes: 784, hiddenNodes: 200, outputNodes: 10, diff --git a/src/controller/constants/types/events.ts b/src/controller/constants/types/events.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/controller/types/constant.ts b/src/controller/constants/types/networkConfig.ts similarity index 75% rename from src/controller/types/constant.ts rename to src/controller/constants/types/networkConfig.ts index f4c95e9..f099979 100644 --- a/src/controller/types/constant.ts +++ b/src/controller/constants/types/networkConfig.ts @@ -1,4 +1,4 @@ -export interface CONSTANT { +export interface networkConfig { inputNodes: number; hiddenNodes: number; outputNodes: number; diff --git a/src/controller/dataStore.js b/src/controller/dataStore.js deleted file mode 100644 index 434e342..0000000 --- a/src/controller/dataStore.js +++ /dev/null @@ -1,42 +0,0 @@ -import eventBus from "./EventBus.js"; -import { DATA_EVENTS } from "./constants/events.js" - -export class DataStore { - constructor() { - this._queryInfo = null; - this._nodeState = {}; - this._queryResult = null; - - eventBus.on(DATA_EVENTS.NODE_UPDATE, this.setQueryInfo.bind(this)); - eventBus.on(DATA_EVENTS.RESULT_UPDATE, this.setQueryResult.bind(this)); - } - - setQueryInfo(queryInfo) { - this._queryInfo = queryInfo; - eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryInfo) - } - getQueryInfo() { - return this._queryInfo; - } - - setNodeState(nodeState) { - this._nodeState = nodeState; - } - getNodeState() { - return this._nodeState; - } - - setQueryResult(queryResult) { - this._queryResult = queryResult; - eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryResult); - } - getQueryResult() { - return this._queryResult; - } - - reset() { - this._queryInfo = null; - this._nodeState = {}; - this._queryResult = null; - } -} \ No newline at end of file diff --git a/src/controller/queryPipeline/DrawingEventHandler.js b/src/controller/queryPipeline/DrawingEventHandler.js index 05671c2..8f0facf 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.js +++ b/src/controller/queryPipeline/DrawingEventHandler.js @@ -1,6 +1,6 @@ -import BoundingBox from "../../view/components/canvasUtils/BoundingBox.js"; -import eventBus from "../EventBus.js"; -import { DRAWING_EVENTS } from "../constants/events.js"; +import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; +import eventBus from '../EventBus.ts'; +import { DRAWING_EVENTS } from '../constants/events.ts'; class DrawingEventHandler { constructor() { @@ -11,20 +11,20 @@ class DrawingEventHandler { registerEvents() { const startDraw = (x, y) => { this.isDrawing = true; - eventBus.emit(DRAWING_EVENTS.START_DRAW, {x, y}); - } + eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y }); + }; const draw = (x, y) => { if (!this.isDrawing) return; BoundingBox.update(x, y); - eventBus.emit(DRAWING_EVENTS.DRAW, {x, y}) - } + eventBus.emit(DRAWING_EVENTS.DRAW, { x, y }); + }; const endDraw = () => { this.isDrawing = false; BoundingBox.log(); eventBus.emit(DRAWING_EVENTS.END_DRAW); - } + }; const getTouchPosition = (e) => { e.preventDefault(); @@ -33,31 +33,32 @@ class DrawingEventHandler { return { x: touch.clientX - rect.left, y: touch.clientY - rect.top, - } - } + }; + }; - this.inputCanvas.addEventListener("mousedown", (e) => { + this.inputCanvas.addEventListener('mousedown', (e) => { startDraw(e.offsetX, e.offsetY); }); - this.inputCanvas.addEventListener("touchstart", (e) => { - let {x, y} = getTouchPosition(e); + this.inputCanvas.addEventListener('touchstart', (e) => { + let { x, y } = getTouchPosition(e); startDraw(x, y); }); - this.inputCanvas.addEventListener("mousemove", (e) => { + this.inputCanvas.addEventListener('mousemove', (e) => { draw(e.offsetX, e.offsetY); }); - this.inputCanvas.addEventListener("touchmove", (e) => { - let {x, y} = getTouchPosition(e); + this.inputCanvas.addEventListener('touchmove', (e) => { + let { x, y } = getTouchPosition(e); draw(x, y); }); - this.inputCanvas.addEventListener("mouseup", endDraw); - this.inputCanvas.addEventListener("mouseout", endDraw); - this.inputCanvas.addEventListener("touchend", (e) => { + this.inputCanvas.addEventListener('mouseup', endDraw); + this.inputCanvas.addEventListener('mouseout', endDraw); + this.inputCanvas.addEventListener('touchend', (e) => { e.preventDefault(); endDraw(); }); + // TODO: Touch/Click 이벤트 코드 리팩토링(중복 없애기) } } diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index a1b0cd4..6b3c956 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -3,7 +3,6 @@ import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js'; import DrawingEventHandler from './DrawingEventHandler.js'; import { pixelExtractor } from './pixelExtractor.js'; import { throttle } from './throttle'; - import { IQueryProcessControllerProps } from './types/QueryProcessController.js'; import { ICanvasBase, @@ -13,6 +12,7 @@ import { IResizeCanvas, } from '../../view/components/userQuery/types/canvas'; import { INeuralNetworkBase } from '../../core/types/NeuralNetworkBase'; +import { IDataStore } from '../types/DataStore'; class QueryProcessController { private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; @@ -20,6 +20,7 @@ class QueryProcessController { private readonly alignCanvas: ICanvasBase & IAlignCanvas; private readonly resizeCanvas: ICanvasBase & IResizeCanvas; private readonly $NN: INeuralNetworkBase; + private readonly $DS: IDataStore; constructor({ userInputCanvas, @@ -27,6 +28,7 @@ class QueryProcessController { alignCanvas, resizeCanvas, $NN, + $DS, }: IQueryProcessControllerProps) { new DrawingEventHandler(); this.userInputCanvas = userInputCanvas; @@ -35,17 +37,12 @@ class QueryProcessController { this.resizeCanvas = resizeCanvas; this.$NN = $NN; + this.$DS = $DS; this.drawingEvent(); + this.query(); } drawingEvent(): void { - const throttleQuery = throttle((inputs) => { - const result: number[] = this.$NN.query(inputs); - if (result) { - eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); - } - }, 100); - eventBus.on(DRAWING_EVENTS.START_DRAW, ({ x, y }) => { this.userInputCanvas.startPath(x, y); this.trackingCanvas.startPath(x, y); @@ -56,13 +53,23 @@ class QueryProcessController { this.alignCanvas.updateCanvasScale(); this.alignCanvas.centralize(this.trackingCanvas.canvas); this.resizeCanvas.downScale(this.alignCanvas.canvas); - throttleQuery(pixelExtractor(this.resizeCanvas)); + this.$DS.setQueryInfo(pixelExtractor(this.resizeCanvas)); }); eventBus.on(DRAWING_EVENTS.END_DRAW, () => { this.userInputCanvas.endPath(); this.trackingCanvas.endPath(); }); } + + query() { + const throttleQuery = throttle((inputs) => { + const result: number[] = this.$NN.query(inputs); + if (result) eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); + }, 100); + + eventBus.on(DATA_EVENTS.QUERY_CHANGED, (inputs: number[]) => throttleQuery(inputs)); + // TODO: type interface 추가하기, 이벤트버스 구조와 쿼리 구조 다시 생각해보기, TS 마이그레이션 + } } export default QueryProcessController; diff --git a/src/controller/queryPipeline/pixelExtractor.js b/src/controller/queryPipeline/pixelExtractor.js deleted file mode 100644 index d4156c7..0000000 --- a/src/controller/queryPipeline/pixelExtractor.js +++ /dev/null @@ -1,28 +0,0 @@ -export const pixelExtractor = (path) => { - const pathToMatrix = (width, height, data) => { - return Array.from({ length: height }, (_, y) => { - return Array.from({ length: width }, (_, x) => { - const i = (y * width + x) * 4; - const [r, g, b, a] = data.slice(i, i + 4); - return { r, g, b, a }; - }); - }); - } - - const grayscaleMatrix = (matrixRGB) => { - return matrixRGB.flatMap(pixelObject => - pixelObject.map(v => { - const RGB_sum = v.r + v.g + v.b; - return Math.round(RGB_sum/3); - })); - } - - const normalize = (grayscaleMatrix) => { - return grayscaleMatrix.map(v => (v/255)*0.99+0.01); - } - - const {width, height, data} = path.ctx.getImageData(0, 0, path.canvas.width, path.canvas.height); - const matrixRGB = pathToMatrix(width, height, data); - const grayScale = grayscaleMatrix(matrixRGB); - return normalize(grayScale); -}; \ No newline at end of file diff --git a/src/controller/queryPipeline/pixelExtractor.ts b/src/controller/queryPipeline/pixelExtractor.ts new file mode 100644 index 0000000..1f43ba4 --- /dev/null +++ b/src/controller/queryPipeline/pixelExtractor.ts @@ -0,0 +1,42 @@ +import { IResizeCanvas, ICanvasBase } from '../../view/components/userQuery/types/canvas'; + +export const pixelExtractor = (path: IResizeCanvas & ICanvasBase) => { + const pathToMatrix = (width: number, height: number, data: Uint8ClampedArray): object[] => { + return Array.from( + { length: height }, + (_: unknown, y: number): { r: number; g: number; b: number; a: number }[] => { + return Array.from( + { length: width }, + (_: unknown, x: number): { r: number; g: number; b: number; a: number } => { + const i: number = (y * width + x) * 4; + const [r, g, b, a] = data.slice(i, i + 4); + return { r, g, b, a }; + }, + ); + }, + ); + }; + + const grayscaleMatrix = (matrixRGB: object[]): number[] => { + return matrixRGB.flatMap((pixelObject: object[]) => + pixelObject.map((v: { r: number; g: number; b: number; a: number }): number => { + const RGB_sum: number = v.r + v.g + v.b; + return Math.round(RGB_sum / 3); + }), + ); + }; + + const normalize = (grayscaleMatrix: number[]): number[] => { + return grayscaleMatrix.map((v) => (v / 255) * 0.99 + 0.01); + }; + + const { width, height, data } = path.ctx.getImageData( + 0, + 0, + path.canvas.width, + path.canvas.height, + ); + const matrixRGB: object[] = pathToMatrix(width, height, data); + const grayScale = grayscaleMatrix(matrixRGB); + return normalize(grayScale); +}; diff --git a/src/controller/queryPipeline/types/DrawingEventHandler.ts b/src/controller/queryPipeline/types/DrawingEventHandler.ts new file mode 100644 index 0000000..d4a36b5 --- /dev/null +++ b/src/controller/queryPipeline/types/DrawingEventHandler.ts @@ -0,0 +1,12 @@ +interface DrawingEventHandlerParams { + startDraw(x: number, y: number): void; + draw(x: number, y: number): void; + endDraw(): void; + getTouchPosition(e: TouchEvent): { x: number; y: number }; + + addEventListener(listener: EventListener): void; +} + +interface DrawingEventHandlerParams { + registerEvents: DrawingEventHandlerParams; +} diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts index 631d61b..b549161 100644 --- a/src/controller/queryPipeline/types/QueryProcessController.ts +++ b/src/controller/queryPipeline/types/QueryProcessController.ts @@ -6,6 +6,7 @@ import { IResizeCanvas, } from '../../../view/components/userQuery/types/canvas'; import { INeuralNetworkBase } from '../../../core/types/NeuralNetworkBase'; +import { IDataStore } from '../../types/DataStore'; export interface IQueryProcessControllerProps { userInputCanvas: ICanvasBase & IUserInputCanvas; @@ -13,4 +14,5 @@ export interface IQueryProcessControllerProps { alignCanvas: ICanvasBase & IAlignCanvas; resizeCanvas: ICanvasBase & IResizeCanvas; $NN: INeuralNetworkBase; + $DS: IDataStore; } diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts new file mode 100644 index 0000000..e6b04f6 --- /dev/null +++ b/src/controller/types/DataStore.ts @@ -0,0 +1,16 @@ +export interface IDataStore { + setQueryInfo(queryInfo: number[]): void; + getQueryInfo(): number[]; + + setNodeState(nodeState: nodeState): void; + getNodeState(): nodeState; + + setQueryResult(queryResult: number[]): void; + getQueryResult(): number[]; +} + +export interface nodeState { + inputNodes: number[]; + hiddenNodes: number[]; + outputNodes: number[]; +} diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts new file mode 100644 index 0000000..f3ad0a7 --- /dev/null +++ b/src/controller/types/eventBus.ts @@ -0,0 +1,31 @@ +import { Matrix2D } from '../../core/ops/types/OpsType'; + +export interface eventPayloads { + // Data handling event + 'query:changed': number[]; + 'node:changed': { inputNodes: number[]; hiddenNodes: number[]; outputNodes: number[] }; + 'result:changed': number[]; + + // Canvas handling event + 'draw:start': { x: number; y: number }; + 'draw:drawing': { x: number; y: number }; + 'draw:end': void; + 'bondingbox:update': { X: number; y: number }; + + 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; +} + +export interface IEventBus { + events: object; + on( + eventName: K, + subscriber: (payload: eventPayloads[K]) => void, + ): void; + + off( + eventName: K, + subscriber: (payload: eventPayloads[K]) => void, + ): void; + + emit(eventName: K, payload: eventPayloads[K]): void; +} diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts index 152ee46..9796c0f 100644 --- a/src/core/NeuralNetworkBase.ts +++ b/src/core/NeuralNetworkBase.ts @@ -4,6 +4,7 @@ import eventBus from '../controller/EventBus.js'; import { DATA_EVENTS } from '../controller/constants/events.js'; import { weights } from '../controller/types/weights'; +import { Matrix2D } from './ops/types/OpsType'; class NeuralNetworkBase { private readonly W_inputToHidden: number[][] | null; @@ -17,21 +18,21 @@ class NeuralNetworkBase { // CNN operations feedForward(inputs: number[]): { - hiddenInputs: number[]; - hiddenOutputs: number[]; - finalOutputs: number[]; + hiddenInputs: Matrix2D; + hiddenOutputs: Matrix2D; + finalOutputs: Matrix2D; } { - const hiddenInputs: number[] = matrixMultiply( + const hiddenInputs: number[][] = matrixMultiply( this.W_inputToHidden, inputs.map((v) => [v]), ); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환. - const hiddenOutputs: number[] = activationFunction(hiddenInputs); - const finalInputs: number[] = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); - const finalOutputs: number[] = activationFunction(finalInputs); + const hiddenOutputs: Matrix2D = activationFunction(hiddenInputs); + const finalInputs: Matrix2D = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); + const finalOutputs: Matrix2D = activationFunction(finalInputs); return { hiddenInputs, hiddenOutputs, finalOutputs }; } - query(inputs: number[]): number[] { + query(inputs: number[]): Matrix2D { const { hiddenInputs, hiddenOutputs, finalOutputs } = this.feedForward(inputs); eventBus.emit(DATA_EVENTS.NODE_UPDATE, { hiddenInputs, hiddenOutputs, finalOutputs }); return finalOutputs; diff --git a/src/core/WeightManager.js b/src/core/WeightManager.js deleted file mode 100644 index 40b49ef..0000000 --- a/src/core/WeightManager.js +++ /dev/null @@ -1,33 +0,0 @@ -import { createRandomWeight } from "./utils/createRandomWeights.js"; -import { loadPretrainedWeights } from "./utils/loadPretrainedWeights.js"; - -class WeightManager { - _cache = null; - - constructor(networkConfig) { - this.config = networkConfig; - this.path = "assets/preTrainedWeights_h200_lr15p.json"; - } - - async getWeights() { - if(this._cache) return this._cache - try{ - const json = await loadPretrainedWeights(this.path); - this._cache = { - W_inputToHidden: json.W_inputToHidden, - W_hiddenToOutput: json.W_hiddenToOutput, - }; - }catch(err){ - console.warn(`[WeightManager] Using random weights due to error: ${err.message}`); - this._cache = { - W_inputToHidden: createRandomWeight(this.config.hiddenNodes, this.config.inputNodes), - W_hiddenToOutput: createRandomWeight(this.config.outputNodes, this.config.hiddenNodes), - }; - } - console.log("Network weights fetched"); - return this._cache; - } - -} - -export default WeightManager; \ No newline at end of file diff --git a/src/core/WeightManager.ts b/src/core/WeightManager.ts new file mode 100644 index 0000000..12f4710 --- /dev/null +++ b/src/core/WeightManager.ts @@ -0,0 +1,43 @@ +import { createRandomWeight } from './utils/createRandomWeights.js'; +import { loadPretrainedWeights } from './utils/loadPretrainedWeights.js'; +import { networkConfig } from '../controller/constants/types/networkConfig'; + +import { weights } from '../controller/types/weights'; + +class WeightManager { + _cache: null | weights = null; + private config: networkConfig; + private readonly path: string; + + constructor(networkConfig: networkConfig) { + this.config = networkConfig; + this.path = 'assets/preTrainedWeights_h200_lr15p.json'; + } + + async getWeights(): Promise { + if (this._cache) return this._cache; + try { + const json = await loadPretrainedWeights(this.path); + this._cache = { + W_inputToHidden: json.W_inputToHidden, + W_hiddenToOutput: json.W_hiddenToOutput, + }; + } catch (err) { + console.warn(`[WeightManager] Using random weights due to error: ${err.message}`); + this._cache = { + W_inputToHidden: createRandomWeight( + this.config.hiddenNodes, + this.config.inputNodes, + ), + W_hiddenToOutput: createRandomWeight( + this.config.outputNodes, + this.config.hiddenNodes, + ), + }; + } + console.log('Network weights fetched'); + return this._cache; + } +} + +export default WeightManager; diff --git a/src/core/ops/matrixOps.js b/src/core/ops/matrixOps.js deleted file mode 100644 index 41247f3..0000000 --- a/src/core/ops/matrixOps.js +++ /dev/null @@ -1,17 +0,0 @@ -export const matrixMultiply = (A, B) => { - A = Array.isArray(A[0]) ? A : [A]; - - if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array'); - else if (A[0].length !== B.length) throw new Error('rows and columns length doesn\'t match'); - - return A.map((row, i) => - B[0].map((_, j) => - row.reduce((sum, _, k) => { - return sum + A[i][k]*B[k][j]; - }, 0) - ) - ) -} - -export const transposeMatrix = matrix => - matrix[0].map((_, idx)=> matrix.map(row => row[idx])) \ No newline at end of file diff --git a/src/core/ops/matrixOps.ts b/src/core/ops/matrixOps.ts new file mode 100644 index 0000000..f6eda6e --- /dev/null +++ b/src/core/ops/matrixOps.ts @@ -0,0 +1,25 @@ +import { Matrix2D } from './types/OpsType'; + +const ensure2DArray = (matrix: number[] | number[][]): number[][] => { + return Array.isArray(matrix[0]) ? (matrix as number[][]) : [matrix as unknown as number[]]; +}; + +export const matrixMultiply = (A: Matrix2D, B: Matrix2D): Matrix2D => { + A = ensure2DArray(A); + + if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array'); + else if (A[0].length !== B.length) throw new Error("rows and columns length doesn't match"); + + return A.map((row: number[], i: number): number[] => + B[0].map((_: unknown, j: number): number => + row.reduce((sum: number, _: unknown, k: number): number => { + return sum + A[i][k] * B[k][j]; + }, 0), + ), + ); +}; + +export const transposeMatrix = (matrix: Matrix2D): Matrix2D => + matrix[0].map((_: unknown, idx: number): number[] => + matrix.map((row: number[]): number => row[idx]), + ); diff --git a/src/core/ops/types/OpsType.ts b/src/core/ops/types/OpsType.ts new file mode 100644 index 0000000..5f6ec6d --- /dev/null +++ b/src/core/ops/types/OpsType.ts @@ -0,0 +1 @@ +export type Matrix2D = number[][]; diff --git a/src/core/utils/createRandomWeights.js b/src/core/utils/createRandomWeights.js deleted file mode 100644 index 8ff2a1f..0000000 --- a/src/core/utils/createRandomWeights.js +++ /dev/null @@ -1,7 +0,0 @@ -export const createRandomWeight = (col, row) => - Array.from({ length: col }, () => - Array.from({ length: row }, () => { - const value = (Math.random() - .5) * 1.99999; - return value; - }) - ); \ No newline at end of file diff --git a/src/core/utils/createRandomWeights.ts b/src/core/utils/createRandomWeights.ts new file mode 100644 index 0000000..b5d3086 --- /dev/null +++ b/src/core/utils/createRandomWeights.ts @@ -0,0 +1,7 @@ +export const createRandomWeight = (col: number, row: number): number[][] => + Array.from({ length: col }, (): number[] => + Array.from({ length: row }, (): number => { + const value: number = (Math.random() - 0.5) * 1.99999; + return value; + }), + ); diff --git a/src/core/utils/loadPretrainedWeights.js b/src/core/utils/loadPretrainedWeights.js deleted file mode 100644 index 9eab7ae..0000000 --- a/src/core/utils/loadPretrainedWeights.js +++ /dev/null @@ -1,5 +0,0 @@ -export async function loadPretrainedWeights(path) { - const response = await fetch(path); - if(!response.ok) throw new Error(`Failed to load weight datas from ${path}`); - return await response.json(); -} \ No newline at end of file diff --git a/src/core/utils/loadPretrainedWeights.ts b/src/core/utils/loadPretrainedWeights.ts new file mode 100644 index 0000000..d10d0e1 --- /dev/null +++ b/src/core/utils/loadPretrainedWeights.ts @@ -0,0 +1,7 @@ +import { weights } from '../../controller/types/weights'; + +export async function loadPretrainedWeights(path: string): Promise { + 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/main.ts b/src/main.ts index 807ff3c..ca0bcf1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,5 @@ import AppController from './controller/AppContoller.js'; -import { DataStore } from "./controller/DataStore.js"; -const App = new AppController({DataStore}); +const App = new AppController(); await App.initialize(); -console.log("App initialized!"); \ No newline at end of file +console.log('App initialized!'); diff --git a/src/view/DataPresnter.ts b/src/view/DataPresnter.ts new file mode 100644 index 0000000..e69de29 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/canvasUtils/BoundingBox.ts b/src/view/components/canvasUtils/BoundingBox.ts new file mode 100644 index 0000000..089bb09 --- /dev/null +++ b/src/view/components/canvasUtils/BoundingBox.ts @@ -0,0 +1,44 @@ +import { IinitialCoords, originalObject } from './types'; + +class BoundingBox { + private readonly initialCoords: IinitialCoords; + private coordinate: IinitialCoords; + private readonly originalObject: originalObject | object; + + constructor() { + this.initialCoords = { + minX: Infinity, + minY: Infinity, + maxX: -Infinity, + maxY: -Infinity, + }; + this.coordinate = { ...this.initialCoords }; + 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/canvasUtils/types.ts b/src/view/components/canvasUtils/types.ts new file mode 100644 index 0000000..b9e8646 --- /dev/null +++ b/src/view/components/canvasUtils/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/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.js index 743ecdc..f84d46c 100644 --- a/src/view/components/userQuery/AlignCanvas.js +++ b/src/view/components/userQuery/AlignCanvas.js @@ -1,4 +1,4 @@ -import BoundingBox from "../canvasUtils/BoundingBox.js"; +import BoundingBox from '../canvasUtils/BoundingBox.ts'; class AlignCanvas { constructor() { @@ -16,12 +16,12 @@ class AlignCanvas { 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; + 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; + this.canvas.width = BoundingBox.originalObject.height * 1.3; + this.canvas.height = BoundingBox.originalObject.height * 1.3; } } @@ -30,13 +30,18 @@ class AlignCanvas { 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 - ); + }; + 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, + ); } clear() { @@ -45,4 +50,4 @@ class AlignCanvas { } } -export default AlignCanvas; \ No newline at end of file +export default AlignCanvas; diff --git a/src/view/components/userQuery/PathTrackingCanvas.js b/src/view/components/userQuery/PathTrackingCanvas.js index 2f0248e..386a61f 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.js +++ b/src/view/components/userQuery/PathTrackingCanvas.js @@ -1,16 +1,16 @@ -import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js"; +import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class PathTrackingCanvas { constructor() { // this.strokeCanvas = document.createElement('pathTrackingCanvas'); this.canvas = document.getElementById('pathTrackingCanvas'); this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); - this.setupCanvas() + this.setupCanvas(); } setupCanvas() { this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight / 3; + this.canvas.height = window.innerHeight / 3; this.ctx.fillStyle = 'rgba(0, 0, 0)'; this.ctx.strokeStyle = 'rgba(255, 255, 255)'; @@ -37,4 +37,4 @@ class PathTrackingCanvas { } } -export default PathTrackingCanvas; \ No newline at end of file +export default PathTrackingCanvas; diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.js index b329245..7ef7f26 100644 --- a/src/view/components/userQuery/UserInputCanvas.js +++ b/src/view/components/userQuery/UserInputCanvas.js @@ -1,4 +1,4 @@ -import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js"; +import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class UserInputCanvas { constructor() { @@ -16,7 +16,7 @@ class UserInputCanvas { clear = () => { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.drawGridDots(); - } + }; setupCanvas() { this.canvas.width = window.innerWidth; @@ -34,7 +34,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) { @@ -58,4 +58,4 @@ class UserInputCanvas { } } -export default UserInputCanvas; \ No newline at end of file +export default UserInputCanvas; From 7e86c81beaec2fc6099983213181f94d575a7163 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 22:17:29 +0900 Subject: [PATCH 13/94] =?UTF-8?q?build:=20package,=20pnpm-lock=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vite --- package.json | 3 +- pnpm-lock.yaml | 576 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 578 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d780ffc..eba51c4 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.3", - "prettier": "3.6.2" + "prettier": "3.6.2", + "vite": "^7.0.6" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ec903a..9c498a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,168 @@ importers: prettier: specifier: 3.6.2 version: 3.6.2 + vite: + specifier: ^7.0.6 + version: 7.0.6 packages: + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -85,6 +244,106 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@rollup/rollup-android-arm-eabi@4.46.2': + resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.46.2': + resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.46.2': + resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.46.2': + resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.46.2': + resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.46.2': + resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.46.2': + resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.46.2': + resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.46.2': + resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.46.2': + resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} + cpu: [x64] + os: [win32] + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -151,6 +410,11 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -229,6 +493,14 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -244,6 +516,11 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -312,6 +589,11 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -339,6 +621,17 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -360,6 +653,11 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + rollup@4.46.2: + resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -368,6 +666,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -380,6 +682,10 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -387,6 +693,46 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite@7.0.6: + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -402,6 +748,84 @@ packages: snapshots: + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)': dependencies: eslint: 9.32.0 @@ -461,6 +885,66 @@ snapshots: '@pkgr/core@0.2.9': {} + '@rollup/rollup-android-arm-eabi@4.46.2': + optional: true + + '@rollup/rollup-android-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.46.2': + optional: true + + '@rollup/rollup-darwin-x64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.46.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.46.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.46.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.46.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.46.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.46.2': + optional: true + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -518,6 +1002,35 @@ snapshots: deep-is@0.1.4: {} + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.32.0): @@ -608,6 +1121,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -624,6 +1141,9 @@ snapshots: flatted@3.3.3: {} + fsevents@2.3.3: + optional: true + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -680,6 +1200,8 @@ snapshots: ms@2.1.3: {} + nanoid@3.3.11: {} + natural-compare@1.4.0: {} optionator@0.9.4: @@ -707,6 +1229,16 @@ snapshots: path-key@3.1.1: {} + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -719,12 +1251,40 @@ snapshots: resolve-from@4.0.0: {} + rollup@4.46.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.46.2 + '@rollup/rollup-android-arm64': 4.46.2 + '@rollup/rollup-darwin-arm64': 4.46.2 + '@rollup/rollup-darwin-x64': 4.46.2 + '@rollup/rollup-freebsd-arm64': 4.46.2 + '@rollup/rollup-freebsd-x64': 4.46.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 + '@rollup/rollup-linux-arm-musleabihf': 4.46.2 + '@rollup/rollup-linux-arm64-gnu': 4.46.2 + '@rollup/rollup-linux-arm64-musl': 4.46.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 + '@rollup/rollup-linux-ppc64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-gnu': 4.46.2 + '@rollup/rollup-linux-riscv64-musl': 4.46.2 + '@rollup/rollup-linux-s390x-gnu': 4.46.2 + '@rollup/rollup-linux-x64-gnu': 4.46.2 + '@rollup/rollup-linux-x64-musl': 4.46.2 + '@rollup/rollup-win32-arm64-msvc': 4.46.2 + '@rollup/rollup-win32-ia32-msvc': 4.46.2 + '@rollup/rollup-win32-x64-msvc': 4.46.2 + fsevents: 2.3.3 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + source-map-js@1.2.1: {} + strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -735,6 +1295,11 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -743,6 +1308,17 @@ snapshots: dependencies: punycode: 2.3.1 + vite@7.0.6: + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.46.2 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + which@2.0.2: dependencies: isexe: 2.0.0 From 54ccb9d392ea13b5899706f8418e1588fcd825c4 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 23:33:38 +0900 Subject: [PATCH 14/94] =?UTF-8?q?chore:=20tsconfig=20'allowImportingTsExte?= =?UTF-8?q?nsions'=20=EC=98=B5=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 0a0b2a2..592c17f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,8 @@ "target": "ES2020", "module": "ES2022", "moduleResolution": "Node", + "allowImportingTsExtensions": true, + "noEmit": true, "outDir": "./dist", "rootDir": "./src", "strict": false, From 1c7769e828cbfa491ed67263f22e1fd0a9c08ea9 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 23:34:55 +0900 Subject: [PATCH 15/94] =?UTF-8?q?migration:=20=EC=BA=94=EB=B2=84=EC=8A=A4?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=A7=81=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81(WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../queryPipeline/DrawingEventHandler.js | 65 ---------------- .../queryPipeline/DrawingEventHandler.ts | 74 +++++++++++++++++++ 2 files changed, 74 insertions(+), 65 deletions(-) delete mode 100644 src/controller/queryPipeline/DrawingEventHandler.js create mode 100644 src/controller/queryPipeline/DrawingEventHandler.ts diff --git a/src/controller/queryPipeline/DrawingEventHandler.js b/src/controller/queryPipeline/DrawingEventHandler.js deleted file mode 100644 index 8f0facf..0000000 --- a/src/controller/queryPipeline/DrawingEventHandler.js +++ /dev/null @@ -1,65 +0,0 @@ -import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; -import eventBus from '../EventBus.ts'; -import { DRAWING_EVENTS } from '../constants/events.ts'; - -class DrawingEventHandler { - constructor() { - this.inputCanvas = document.getElementById('userInputCanvas'); - this.registerEvents(); - } - - registerEvents() { - const startDraw = (x, y) => { - this.isDrawing = true; - eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y }); - }; - - const draw = (x, y) => { - if (!this.isDrawing) return; - BoundingBox.update(x, y); - eventBus.emit(DRAWING_EVENTS.DRAW, { x, y }); - }; - - const endDraw = () => { - this.isDrawing = false; - BoundingBox.log(); - eventBus.emit(DRAWING_EVENTS.END_DRAW); - }; - - const getTouchPosition = (e) => { - e.preventDefault(); - const touch = e.touches[0]; - const rect = this.inputCanvas.getBoundingClientRect(); - return { - x: touch.clientX - rect.left, - y: touch.clientY - rect.top, - }; - }; - - this.inputCanvas.addEventListener('mousedown', (e) => { - startDraw(e.offsetX, e.offsetY); - }); - this.inputCanvas.addEventListener('touchstart', (e) => { - let { x, y } = getTouchPosition(e); - startDraw(x, y); - }); - - this.inputCanvas.addEventListener('mousemove', (e) => { - draw(e.offsetX, e.offsetY); - }); - this.inputCanvas.addEventListener('touchmove', (e) => { - let { x, y } = getTouchPosition(e); - draw(x, y); - }); - - this.inputCanvas.addEventListener('mouseup', endDraw); - this.inputCanvas.addEventListener('mouseout', endDraw); - this.inputCanvas.addEventListener('touchend', (e) => { - e.preventDefault(); - endDraw(); - }); - // TODO: Touch/Click 이벤트 코드 리팩토링(중복 없애기) - } -} - -export default DrawingEventHandler; diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts new file mode 100644 index 0000000..f86439a --- /dev/null +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -0,0 +1,74 @@ +import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; +import eventBus from '../EventBus.ts'; +import { DRAWING_EVENTS } from '../constants/events.ts'; + +class DrawingEventHandler { + private inputCanvas: HTMLCanvasElement | null; + private isDrawing: boolean; + // TODO: 이벤트 핸들러 리팩토링 + + constructor() { + this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; + this.isDrawing = false; + this.registerEvents(); + } + + registerEvents(): void { + this.inputCanvas.addEventListener('mousedown', this.handleStartDraw); + this.inputCanvas.addEventListener('touchstart', this.handleStartDraw); + this.inputCanvas.addEventListener('mousemove', this.handleDraw); + this.inputCanvas.addEventListener('touchmove', this.handleDraw); + this.inputCanvas.addEventListener('mouseup', this.endDraw); + this.inputCanvas.addEventListener('mouseout', this.endDraw); + this.inputCanvas.addEventListener('touchend', (e: TouchEvent): void => { + e.preventDefault(); + this.endDraw(); + }); + } + + startDraw = (x: number, y: number): void => { + this.isDrawing = true; + eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y }); + }; + + draw = (x: number, y: number): void => { + if (!this.isDrawing) return; + BoundingBox.update(x, y); + eventBus.emit(DRAWING_EVENTS.DRAW, { x, y }); + }; + + endDraw = (): void => { + this.isDrawing = false; + BoundingBox.log(); + eventBus.emit(DRAWING_EVENTS.END_DRAW, null); + }; + + getTouchPosition = (e: TouchEvent): { x: number; y: number } => { + e.preventDefault(); + const touch = e.touches[0]; + const rect = this.inputCanvas.getBoundingClientRect(); + return { + x: touch.clientX - rect.left, + y: touch.clientY - rect.top, + }; + }; + + handleStartDraw = (e: MouseEvent | TouchEvent): void => { + if (e instanceof MouseEvent) { + this.startDraw(e.offsetX, e.offsetY); + } else if (e instanceof TouchEvent) { + let { x, y } = this.getTouchPosition(e); + this.startDraw(x, y); + } + }; + handleDraw = (e: MouseEvent | TouchEvent): void => { + if (e instanceof MouseEvent) { + this.draw(e.offsetX, e.offsetY); + } else if (e instanceof TouchEvent) { + let { x, y } = this.getTouchPosition(e); + this.draw(x, y); + } + }; +} + +export default DrawingEventHandler; From d7f0725b66dc927f372b43031f85e88890562acb Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 23:41:47 +0900 Subject: [PATCH 16/94] =?UTF-8?q?chore:=20=EC=84=A4=EB=AA=85=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/queryPipeline/DrawingEventHandler.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index f86439a..04c22bb 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -26,6 +26,7 @@ class DrawingEventHandler { }); } + // eventBus로 입력 event 전송 startDraw = (x: number, y: number): void => { this.isDrawing = true; eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y }); @@ -43,6 +44,7 @@ class DrawingEventHandler { eventBus.emit(DRAWING_EVENTS.END_DRAW, null); }; + // 캔버스에서의 터치 위치 반환 getTouchPosition = (e: TouchEvent): { x: number; y: number } => { e.preventDefault(); const touch = e.touches[0]; @@ -53,6 +55,7 @@ class DrawingEventHandler { }; }; + // 마우스 클릭, 터치 이벤트에 대한 입력 이벤트 발생 handleStartDraw = (e: MouseEvent | TouchEvent): void => { if (e instanceof MouseEvent) { this.startDraw(e.offsetX, e.offsetY); @@ -61,6 +64,7 @@ class DrawingEventHandler { this.startDraw(x, y); } }; + handleDraw = (e: MouseEvent | TouchEvent): void => { if (e instanceof MouseEvent) { this.draw(e.offsetX, e.offsetY); From a5874df9a15f5449215ce6e7d5f4ce45444576f5 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 6 Aug 2025 23:54:01 +0900 Subject: [PATCH 17/94] =?UTF-8?q?refactor:=20DrawingEventHandler=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../queryPipeline/DrawingEventHandler.ts | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index 04c22bb..109d327 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -5,7 +5,6 @@ import { DRAWING_EVENTS } from '../constants/events.ts'; class DrawingEventHandler { private inputCanvas: HTMLCanvasElement | null; private isDrawing: boolean; - // TODO: 이벤트 핸들러 리팩토링 constructor() { this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; @@ -14,16 +13,25 @@ class DrawingEventHandler { } registerEvents(): void { - this.inputCanvas.addEventListener('mousedown', this.handleStartDraw); - this.inputCanvas.addEventListener('touchstart', this.handleStartDraw); - this.inputCanvas.addEventListener('mousemove', this.handleDraw); - this.inputCanvas.addEventListener('touchmove', this.handleDraw); - this.inputCanvas.addEventListener('mouseup', this.endDraw); - this.inputCanvas.addEventListener('mouseout', this.endDraw); - this.inputCanvas.addEventListener('touchend', (e: TouchEvent): void => { - e.preventDefault(); - this.endDraw(); - }); + const bindings: [string, EventListener][] = [ + ['mousedown', this.handleStartDraw], + ['touchstart', this.handleStartDraw], + ['mousemove', this.handleDraw], + ['touchmove', this.handleDraw], + ['mouseup', this.endDraw], + ['mouseout', this.endDraw], + [ + 'touchend', + (e: TouchEvent): void => { + e.preventDefault(); + this.endDraw(); + }, + ], + ]; + + bindings.forEach(([event, handler]: [string, EventListener]): void => + this.inputCanvas.addEventListener(event, handler), + ); } // eventBus로 입력 event 전송 From a88d829eb2c4001d43cae436cb7f5ead375bfa49 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Thu, 7 Aug 2025 19:49:20 +0900 Subject: [PATCH 18/94] refactor: migration activation ops --- src/core/ops/activationOps.js | 17 ----------------- src/core/ops/activationOps.ts | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 17 deletions(-) delete mode 100644 src/core/ops/activationOps.js create mode 100644 src/core/ops/activationOps.ts diff --git a/src/core/ops/activationOps.js b/src/core/ops/activationOps.js deleted file mode 100644 index 9dfb928..0000000 --- a/src/core/ops/activationOps.js +++ /dev/null @@ -1,17 +0,0 @@ -const activationFunction = matrix => - matrix.map(array => - array.map(v => { - const value = sigmoid(v) - return parseFloat(value.toFixed(5)) //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림 - }) - ) - -const sigmoid = ($x) => { - return 1 / (1 + Math.exp(-$x)); -}; - -const ReLU = ($x) => { - return Math.max(0, $x); -} - -export default activationFunction; \ No newline at end of file diff --git a/src/core/ops/activationOps.ts b/src/core/ops/activationOps.ts new file mode 100644 index 0000000..3d92e4a --- /dev/null +++ b/src/core/ops/activationOps.ts @@ -0,0 +1,19 @@ +import { Matrix2D } from './types/OpsType.ts'; + +const activationFunction = (matrix: Matrix2D): Matrix2D => + matrix.map((array: number[]): number[] => + array.map((v: number): number => { + const value: number = sigmoid(v); + return parseFloat(value.toFixed(5)); //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림 + }), + ); + +const sigmoid = ($x: number): number => { + return 1 / (1 + Math.exp(-$x)); +}; + +const ReLU = ($x: number): number => { + return Math.max(0, $x); +}; + +export default activationFunction; From 7cc833856ffa8f1fb0c11e0e5b511eb7211224db Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Thu, 7 Aug 2025 22:15:53 +0900 Subject: [PATCH 19/94] refactor: canvas migration to Typescript --- .../userQuery/{AlignCanvas.js => AlignCanvas.ts} | 16 ++++++++-------- ...thTrackingCanvas.js => PathTrackingCanvas.ts} | 14 ++++++++------ .../{ResizeCanvas.js => ResizeCanvas.ts} | 9 ++++++--- .../{UserInputCanvas.js => UserInputCanvas.ts} | 16 ++++++++++------ 4 files changed, 32 insertions(+), 23 deletions(-) rename src/view/components/userQuery/{AlignCanvas.js => AlignCanvas.ts} (84%) rename src/view/components/userQuery/{PathTrackingCanvas.js => PathTrackingCanvas.ts} (73%) rename src/view/components/userQuery/{ResizeCanvas.js => ResizeCanvas.ts} (67%) rename src/view/components/userQuery/{UserInputCanvas.js => UserInputCanvas.ts} (82%) diff --git a/src/view/components/userQuery/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.ts similarity index 84% rename from src/view/components/userQuery/AlignCanvas.js rename to src/view/components/userQuery/AlignCanvas.ts index f84d46c..7aa43af 100644 --- a/src/view/components/userQuery/AlignCanvas.js +++ b/src/view/components/userQuery/AlignCanvas.ts @@ -1,21 +1,21 @@ import BoundingBox from '../canvasUtils/BoundingBox.ts'; class AlignCanvas { + private readonly canvas: HTMLCanvasElement | null; + private readonly ctx: CanvasRenderingContext2D; + constructor() { - // this.canvas = document.createElement('alignCanvas'); - this.canvas = document.getElementById('alignCanvas'); + this.canvas = document.createElement('canvas') as HTMLCanvasElement; 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() { + setupCanvas(): void { this.ctx.fillStyle = 'rgba(0, 0, 0)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.updateCanvasScale(); } - updateCanvasScale() { + updateCanvasScale(): void { if (BoundingBox.originalObject.width > BoundingBox.originalObject.height) { this.canvas.width = BoundingBox.originalObject.width * 1.3; this.canvas.height = BoundingBox.originalObject.width * 1.3; @@ -25,7 +25,7 @@ class AlignCanvas { } } - centralize(path) { + centralize(path: HTMLImageElement): void { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); const alignStartPosition = { x: (this.canvas.width - BoundingBox.originalObject.width) / 2, @@ -44,7 +44,7 @@ class AlignCanvas { ); } - clear() { + clear(): void { this.canvas.width = this.canvas.height = 1; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } diff --git a/src/view/components/userQuery/PathTrackingCanvas.js b/src/view/components/userQuery/PathTrackingCanvas.ts similarity index 73% rename from src/view/components/userQuery/PathTrackingCanvas.js rename to src/view/components/userQuery/PathTrackingCanvas.ts index 386a61f..f264489 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.js +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -1,14 +1,16 @@ import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class PathTrackingCanvas { + private readonly canvas: HTMLCanvasElement | null; + private readonly ctx: CanvasRenderingContext2D; + constructor() { - // this.strokeCanvas = document.createElement('pathTrackingCanvas'); - this.canvas = document.getElementById('pathTrackingCanvas'); + this.canvas = document.createElement('canvas') as HTMLCanvasElement; this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); this.setupCanvas(); } - setupCanvas() { + setupCanvas(): void { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight / 3; @@ -22,17 +24,17 @@ class PathTrackingCanvas { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } - 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(); } } diff --git a/src/view/components/userQuery/ResizeCanvas.js b/src/view/components/userQuery/ResizeCanvas.ts similarity index 67% rename from src/view/components/userQuery/ResizeCanvas.js rename to src/view/components/userQuery/ResizeCanvas.ts index cd0a657..82b36b6 100644 --- a/src/view/components/userQuery/ResizeCanvas.js +++ b/src/view/components/userQuery/ResizeCanvas.ts @@ -1,17 +1,20 @@ class ResizeCanvas { + private readonly canvas: HTMLCanvasElement | null; + private readonly ctx: CanvasRenderingContext2D; + constructor() { - this.canvas = document.getElementById("resizeCanvas"); + this.canvas = document.createElement("canvas") as HTMLCanvasElement; this.ctx = this.canvas.getContext("2d", { willReadFrequently: true }); this.canvas.width = this.canvas.height = 28; } - downScale(path) { + downScale(path: HTMLImageElement): void { 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); } - clear() { + clear(): void { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } } diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.ts similarity index 82% rename from src/view/components/userQuery/UserInputCanvas.js rename to src/view/components/userQuery/UserInputCanvas.ts index 7ef7f26..7da0f85 100644 --- a/src/view/components/userQuery/UserInputCanvas.js +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -1,8 +1,12 @@ import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class UserInputCanvas { + private readonly canvas: HTMLCanvasElement | null; + private readonly ctx: CanvasRenderingContext2D; + private isDrawing: boolean; + constructor() { - this.canvas = document.getElementById('userInputCanvas'); + this.canvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; this.ctx = this.canvas.getContext('2d'); this.isDrawing = false; this.setupCanvas(); @@ -13,12 +17,12 @@ class UserInputCanvas { this.ctx.lineJoin = CANVAS_CONFIG.lineJoin; } - clear = () => { + clear = (): void => { this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.drawGridDots(); }; - setupCanvas() { + setupCanvas(): void { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight / 3; @@ -43,17 +47,17 @@ 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(); } } From 54db197143f99f1ce724dcdafc28fb30b746df0c Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 9 Aug 2025 23:48:27 +0900 Subject: [PATCH 20/94] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/{AppContoller.js => AppController.js} | 8 ++++---- src/main.ts | 2 +- .../perceptron/{Perceptron.js => Perceptron.ts} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename src/controller/{AppContoller.js => AppController.js} (96%) rename src/view/components/perceptron/{Perceptron.js => Perceptron.ts} (100%) diff --git a/src/controller/AppContoller.js b/src/controller/AppController.js similarity index 96% rename from src/controller/AppContoller.js rename to src/controller/AppController.js index eca5910..001b9e9 100644 --- a/src/controller/AppContoller.js +++ b/src/controller/AppController.js @@ -1,10 +1,10 @@ import WeightManager from '../core/WeightManager.ts'; import NeuralNetworkBase from '../core/NeuralNetworkBase.ts'; import QueryProcessController from './queryPipeline/QueryProcessController.ts'; -import UserInputCanvas from '../view/components/userQuery/UserInputCanvas.js'; -import PathTrackingCanvas from '../view/components/userQuery/PathTrackingCanvas.js'; -import AlignCanvas from '../view/components/userQuery/AlignCanvas.js'; -import ResizeCanvas from '../view/components/userQuery/ResizeCanvas.js'; +import UserInputCanvas from '../view/components/userQuery/UserInputCanvas.ts'; +import PathTrackingCanvas from '../view/components/userQuery/PathTrackingCanvas.ts'; +import AlignCanvas from '../view/components/userQuery/AlignCanvas.ts'; +import ResizeCanvas from '../view/components/userQuery/ResizeCanvas.ts'; import DataStore from './DataStore.ts'; import eventBus from './EventBus.ts'; diff --git a/src/main.ts b/src/main.ts index ca0bcf1..70a5ce8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import AppController from './controller/AppContoller.js'; +import AppController from './controller/AppController.js'; const App = new AppController(); await App.initialize(); diff --git a/src/view/components/perceptron/Perceptron.js b/src/view/components/perceptron/Perceptron.ts similarity index 100% rename from src/view/components/perceptron/Perceptron.js rename to src/view/components/perceptron/Perceptron.ts From 40fd0b9bd64247c3ede8a118607de15f0d0a2289 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 9 Aug 2025 23:49:05 +0900 Subject: [PATCH 21/94] =?UTF-8?q?fix:=20BoundingBox=EC=9D=98=20=EC=98=A4?= =?UTF-8?q?=EB=B8=8C=EC=A0=9D=ED=8A=B8=20=EC=A2=8C=ED=91=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=99=B8=EB=B6=80=EC=97=90=EC=84=9C=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/components/canvasUtils/BoundingBox.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/view/components/canvasUtils/BoundingBox.ts b/src/view/components/canvasUtils/BoundingBox.ts index 089bb09..8aaac15 100644 --- a/src/view/components/canvasUtils/BoundingBox.ts +++ b/src/view/components/canvasUtils/BoundingBox.ts @@ -3,7 +3,7 @@ import { IinitialCoords, originalObject } from './types'; class BoundingBox { private readonly initialCoords: IinitialCoords; private coordinate: IinitialCoords; - private readonly originalObject: originalObject | object; + readonly originalObject: originalObject; constructor() { this.initialCoords = { @@ -13,7 +13,12 @@ class BoundingBox { maxY: -Infinity, }; this.coordinate = { ...this.initialCoords }; - this.originalObject = {}; + this.originalObject = { + x: 0, + y: 0, + width: 0, + height: 0, + }; } update(currentX: number, currentY: number): void { From 9cf8460637fb84842ffde5778728fba84d4055e7 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 15:03:25 +0900 Subject: [PATCH 22/94] =?UTF-8?q?chore:=20drawingEvent=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/queryPipeline/QueryProcessController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index 6b3c956..00d4dfb 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -38,11 +38,11 @@ class QueryProcessController { this.$NN = $NN; this.$DS = $DS; - this.drawingEvent(); + this.registerDrawingEvent(); this.query(); } - drawingEvent(): void { + registerDrawingEvent(): void { eventBus.on(DRAWING_EVENTS.START_DRAW, ({ x, y }) => { this.userInputCanvas.startPath(x, y); this.trackingCanvas.startPath(x, y); From dd86942acc268358b706f6bac044ee506f116821 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 16:10:30 +0900 Subject: [PATCH 23/94] =?UTF-8?q?refactor:=20BoundingBox=EC=9D=98=20?= =?UTF-8?q?=EC=BA=A1=EC=8A=90=ED=99=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20get?= =?UTF-8?q?ter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/canvasUtils/BoundingBox.ts | 6 ++++- src/view/components/userQuery/AlignCanvas.ts | 26 +++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/view/components/canvasUtils/BoundingBox.ts b/src/view/components/canvasUtils/BoundingBox.ts index 8aaac15..bd7b1b1 100644 --- a/src/view/components/canvasUtils/BoundingBox.ts +++ b/src/view/components/canvasUtils/BoundingBox.ts @@ -3,7 +3,7 @@ import { IinitialCoords, originalObject } from './types'; class BoundingBox { private readonly initialCoords: IinitialCoords; private coordinate: IinitialCoords; - readonly originalObject: originalObject; + private originalObject: originalObject; constructor() { this.initialCoords = { @@ -21,6 +21,10 @@ class BoundingBox { }; } + getOriginalObject(): Readonly { + return { ...this.originalObject }; + } + update(currentX: number, currentY: number): void { this.coordinate = { minX: Math.round(Math.min(this.coordinate.minX, currentX)), diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts index 7aa43af..4809f51 100644 --- a/src/view/components/userQuery/AlignCanvas.ts +++ b/src/view/components/userQuery/AlignCanvas.ts @@ -16,31 +16,31 @@ class AlignCanvas { this.updateCanvasScale(); } updateCanvasScale(): void { - if (BoundingBox.originalObject.width > BoundingBox.originalObject.height) { - this.canvas.width = BoundingBox.originalObject.width * 1.3; - this.canvas.height = BoundingBox.originalObject.width * 1.3; + 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.originalObject.height * 1.3; - this.canvas.height = BoundingBox.originalObject.height * 1.3; + this.canvas.width = BoundingBox.getOriginalObject().height * 1.3; + this.canvas.height = BoundingBox.getOriginalObject().height * 1.3; } } centralize(path: HTMLImageElement): void { 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, + x: (this.canvas.width - BoundingBox.getOriginalObject().width) / 2, + y: (this.canvas.height - BoundingBox.getOriginalObject().height) / 2, }; this.ctx.drawImage( path, - BoundingBox.originalObject.x, - BoundingBox.originalObject.y, - BoundingBox.originalObject.width, - BoundingBox.originalObject.height, + BoundingBox.getOriginalObject().x, + BoundingBox.getOriginalObject().y, + BoundingBox.getOriginalObject().width, + BoundingBox.getOriginalObject().height, alignStartPosition.x, alignStartPosition.y, - BoundingBox.originalObject.width, - BoundingBox.originalObject.height, + BoundingBox.getOriginalObject().width, + BoundingBox.getOriginalObject().height, ); } From fcd057d5e5acb845d0e3b7559201abc78ee93120 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 16:10:47 +0900 Subject: [PATCH 24/94] chore: js to ts --- src/view/{ViewHandler.js => ViewHandler.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/view/{ViewHandler.js => ViewHandler.ts} (100%) diff --git a/src/view/ViewHandler.js b/src/view/ViewHandler.ts similarity index 100% rename from src/view/ViewHandler.js rename to src/view/ViewHandler.ts From 9911f1dd78ac682414d861eee0cd765384924ac3 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 16:11:53 +0900 Subject: [PATCH 25/94] =?UTF-8?q?refactor:=20entryPoint=EC=9D=98=20?= =?UTF-8?q?=EC=9D=BC=EA=B4=80=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20Dra?= =?UTF-8?q?wingEventHandler=EC=9D=98=20=EC=84=A0=EC=96=B8=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.js | 2 ++ src/controller/queryPipeline/QueryProcessController.ts | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/AppController.js b/src/controller/AppController.js index 001b9e9..b1c3eb9 100644 --- a/src/controller/AppController.js +++ b/src/controller/AppController.js @@ -10,12 +10,14 @@ import DataStore from './DataStore.ts'; import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; +import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; class AppController { async initialize() { const $WM = new WeightManager(NETWORK_CONFIG); const $NN = new NeuralNetworkBase(await $WM.getWeights()); const $DS = new DataStore(); + new DrawingEventHandler(); const $QC = new QueryProcessController({ userInputCanvas: new UserInputCanvas(), trackingCanvas: new PathTrackingCanvas(), diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index 00d4dfb..55499f5 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -1,6 +1,5 @@ import eventBus from '../EventBus.js'; import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js'; -import DrawingEventHandler from './DrawingEventHandler.js'; import { pixelExtractor } from './pixelExtractor.js'; import { throttle } from './throttle'; import { IQueryProcessControllerProps } from './types/QueryProcessController.js'; @@ -30,7 +29,6 @@ class QueryProcessController { $NN, $DS, }: IQueryProcessControllerProps) { - new DrawingEventHandler(); this.userInputCanvas = userInputCanvas; this.trackingCanvas = trackingCanvas; this.alignCanvas = alignCanvas; From ec63410692ef346eeab345d14b795f71da7e57be Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 16:33:26 +0900 Subject: [PATCH 26/94] =?UTF-8?q?refactor:=20jsconfig=20path=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jsconfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index ec6c9e9..0afe90a 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,9 +2,7 @@ "compilerOptions": { "baseUrl": "src", "paths": { - "@controller/*": ["controller/*"], - "@core/*": ["core/*"], - "@view/*": ["view/*"] + "@/*": ["./*"] } }, "include": ["src"] From 848c866b57e028ff59de3a84b96a639ce64e3903 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 22:45:32 +0900 Subject: [PATCH 27/94] =?UTF-8?q?feat:=20canvas=20clear=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 4 +--- src/controller/queryPipeline/DrawingEventHandler.ts | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 7f8501c..3e0130a 100644 --- a/index.html +++ b/index.html @@ -20,9 +20,7 @@
- - - +
diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index 109d327..44905ff 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -4,10 +4,12 @@ import { DRAWING_EVENTS } from '../constants/events.ts'; class DrawingEventHandler { private inputCanvas: HTMLCanvasElement | null; + private clearButton: HTMLButtonElement | null; private isDrawing: boolean; constructor() { this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; + this.clearButton = document.getElementById('clear') as HTMLButtonElement; this.isDrawing = false; this.registerEvents(); } @@ -32,6 +34,10 @@ class DrawingEventHandler { bindings.forEach(([event, handler]: [string, EventListener]): void => this.inputCanvas.addEventListener(event, handler), ); + + this.clearButton.addEventListener('click', () => { + eventBus.emit(DRAWING_EVENTS.CLEAR_DRAW, null); + }); } // eventBus로 입력 event 전송 From 75a8e5f3da696248f7294548ff734ce7fec94fb3 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 22:47:19 +0900 Subject: [PATCH 28/94] =?UTF-8?q?feat:=20type=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=98=EB=AA=BB=EB=90=9C=20type=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/DataStore.ts | 5 +-- src/controller/types/eventBus.ts | 3 +- src/core/WeightManager.ts | 2 +- src/core/types/NeuralNetworkBase.ts | 10 ++++-- src/view/components/userQuery/AlignCanvas.ts | 8 ++--- .../userQuery/PathTrackingCanvas.ts | 8 +++-- src/view/components/userQuery/ResizeCanvas.ts | 33 ++++++++++++++----- .../components/userQuery/UserInputCanvas.ts | 5 +-- src/view/components/userQuery/types/canvas.ts | 2 +- 9 files changed, 52 insertions(+), 24 deletions(-) diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts index eecdd50..0b0d47c 100644 --- a/src/controller/DataStore.ts +++ b/src/controller/DataStore.ts @@ -2,11 +2,12 @@ import eventBus from './EventBus.js'; import { DATA_EVENTS } from './constants/events.js'; import { nodeState } from './types/DataStore'; +import { Matrix2D } from '../core/ops/types/OpsType.ts'; class DataStore { private queryInfo: number[] | null = null; private nodeState: nodeState | null = null; - private queryResult: number[] | null = null; + private queryResult: Matrix2D | null = null; constructor() { this.queryInfo = null; @@ -31,7 +32,7 @@ class DataStore { return this.nodeState; } - setQueryResult(queryResult: number[]): void { + setQueryResult(queryResult: Matrix2D): void { if (queryResult === null) throw new Error('No query result found'); this.queryResult = queryResult; eventBus.emit(DATA_EVENTS.RESULT_CHANGED, queryResult); diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts index f3ad0a7..0697741 100644 --- a/src/controller/types/eventBus.ts +++ b/src/controller/types/eventBus.ts @@ -4,12 +4,13 @@ export interface eventPayloads { // Data handling event 'query:changed': number[]; 'node:changed': { inputNodes: number[]; hiddenNodes: number[]; outputNodes: number[] }; - 'result:changed': number[]; + 'result:changed': Matrix2D; // Canvas handling event 'draw:start': { x: number; y: number }; 'draw:drawing': { x: number; y: number }; 'draw:end': void; + 'draw:clear': void; 'bondingbox:update': { X: number; y: number }; 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; diff --git a/src/core/WeightManager.ts b/src/core/WeightManager.ts index 12f4710..1c42a93 100644 --- a/src/core/WeightManager.ts +++ b/src/core/WeightManager.ts @@ -5,7 +5,7 @@ import { networkConfig } from '../controller/constants/types/networkConfig'; import { weights } from '../controller/types/weights'; class WeightManager { - _cache: null | weights = null; + private _cache: null | weights = null; private config: networkConfig; private readonly path: string; diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts index 7035612..98a66e9 100644 --- a/src/core/types/NeuralNetworkBase.ts +++ b/src/core/types/NeuralNetworkBase.ts @@ -1,4 +1,10 @@ +import { Matrix2D } from '../ops/types/OpsType.ts'; + export interface INeuralNetworkBase { - feedForward(inputs: number[]): { hiddenInputs: number[]; hiddenOutputs: number[] }; - query(inputs: number[]): number[]; + feedForward(inputs: number[]): { + hiddenInputs: Matrix2D; + hiddenOutputs: Matrix2D; + finalOutputs: Matrix2D; + }; + query(inputs: number[]): Matrix2D; } diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts index 4809f51..691f45c 100644 --- a/src/view/components/userQuery/AlignCanvas.ts +++ b/src/view/components/userQuery/AlignCanvas.ts @@ -1,8 +1,8 @@ import BoundingBox from '../canvasUtils/BoundingBox.ts'; class AlignCanvas { - private readonly canvas: HTMLCanvasElement | null; - private readonly ctx: CanvasRenderingContext2D; + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; constructor() { this.canvas = document.createElement('canvas') as HTMLCanvasElement; @@ -25,7 +25,7 @@ class AlignCanvas { } } - centralize(path: HTMLImageElement): void { + 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, @@ -46,7 +46,7 @@ class AlignCanvas { clear(): void { this.canvas.width = this.canvas.height = 1; - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.setupCanvas(); } } diff --git a/src/view/components/userQuery/PathTrackingCanvas.ts b/src/view/components/userQuery/PathTrackingCanvas.ts index f264489..72a4e07 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.ts +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -1,8 +1,8 @@ import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class PathTrackingCanvas { - private readonly canvas: HTMLCanvasElement | null; - private readonly ctx: CanvasRenderingContext2D; + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; constructor() { this.canvas = document.createElement('canvas') as HTMLCanvasElement; @@ -37,6 +37,10 @@ class PathTrackingCanvas { endPath(): void { this.ctx.closePath(); } + + clear(): void { + this.setupCanvas(); + } } export default PathTrackingCanvas; diff --git a/src/view/components/userQuery/ResizeCanvas.ts b/src/view/components/userQuery/ResizeCanvas.ts index 82b36b6..96a4275 100644 --- a/src/view/components/userQuery/ResizeCanvas.ts +++ b/src/view/components/userQuery/ResizeCanvas.ts @@ -1,22 +1,37 @@ class ResizeCanvas { - private readonly canvas: HTMLCanvasElement | null; - private readonly ctx: CanvasRenderingContext2D; + 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.width = this.canvas.height = 28; + this.canvas = document.createElement('canvas') as HTMLCanvasElement; + this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + this.setupCanvas(); } - downScale(path: HTMLImageElement): void { + 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); - this.ctx.drawImage(path, 0, 0, path.width, path.height, 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.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.setupCanvas(); + // this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); } } -export default ResizeCanvas; \ No newline at end of file +export default ResizeCanvas; diff --git a/src/view/components/userQuery/UserInputCanvas.ts b/src/view/components/userQuery/UserInputCanvas.ts index 7da0f85..1c57361 100644 --- a/src/view/components/userQuery/UserInputCanvas.ts +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -1,8 +1,8 @@ import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; class UserInputCanvas { - private readonly canvas: HTMLCanvasElement | null; - private readonly ctx: CanvasRenderingContext2D; + public readonly canvas: HTMLCanvasElement | null; + public readonly ctx: CanvasRenderingContext2D; private isDrawing: boolean; constructor() { @@ -18,6 +18,7 @@ class UserInputCanvas { } clear = (): void => { + this.ctx.fillStyle = 'rgba(40,40,40)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.drawGridDots(); }; diff --git a/src/view/components/userQuery/types/canvas.ts b/src/view/components/userQuery/types/canvas.ts index 446c51c..7d0936a 100644 --- a/src/view/components/userQuery/types/canvas.ts +++ b/src/view/components/userQuery/types/canvas.ts @@ -20,4 +20,4 @@ export interface IAlignCanvas { export interface IResizeCanvas { downScale(path: HTMLCanvasElement): void; -} \ No newline at end of file +} From 43fae54acd772c8ec2f9338bcebfa27853474588 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 22:48:01 +0900 Subject: [PATCH 29/94] refactor: migration to Typescript --- src/controller/{AppController.js => AppController.ts} | 11 +++++++++-- src/core/types/WeightManager.ts | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) rename src/controller/{AppController.js => AppController.ts} (77%) create mode 100644 src/core/types/WeightManager.ts diff --git a/src/controller/AppController.js b/src/controller/AppController.ts similarity index 77% rename from src/controller/AppController.js rename to src/controller/AppController.ts index b1c3eb9..58a55ba 100644 --- a/src/controller/AppController.js +++ b/src/controller/AppController.ts @@ -11,8 +11,16 @@ import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; +import { IWeightManager } from '../core/types/WeightManager.ts'; +import { INeuralNetworkBase } from '../core/types/NeuralNetworkBase.ts'; +import { IDataStore } from './types/DataStore.ts'; +import { Matrix2D } from '../core/ops/types/OpsType.ts'; class AppController { + private $WM: IWeightManager; + private $NN: INeuralNetworkBase; + private $DS: IDataStore; + async initialize() { const $WM = new WeightManager(NETWORK_CONFIG); const $NN = new NeuralNetworkBase(await $WM.getWeights()); @@ -26,8 +34,7 @@ class AppController { $NN: $NN, $DS: $DS, }); - eventBus.emit(HANDLER_EVENTS.APP_READY, this.dataStore); - eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => { + eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { console.log('RESULT CHANGED', data); }); } diff --git a/src/core/types/WeightManager.ts b/src/core/types/WeightManager.ts new file mode 100644 index 0000000..5473e62 --- /dev/null +++ b/src/core/types/WeightManager.ts @@ -0,0 +1,9 @@ +import { weights } from '../../controller/types/weights.ts'; +import { networkConfig } from '../../controller/constants/types/networkConfig.ts'; + +export interface IWeightManager { + _cache: null | weights; + config: networkConfig; + path: string; + getWeights(): Promise; +} From 22e7c40f71db14d6a44ad82bd63cd9e3c37668d3 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 22:48:57 +0900 Subject: [PATCH 30/94] =?UTF-8?q?chore:=20canvas=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/events.ts | 1 + .../queryPipeline/QueryProcessController.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/controller/constants/events.ts b/src/controller/constants/events.ts index 7c6ad31..bf0e08c 100644 --- a/src/controller/constants/events.ts +++ b/src/controller/constants/events.ts @@ -17,6 +17,7 @@ export const DRAWING_EVENTS = Object.freeze({ START_DRAW: 'draw:start', DRAW: 'draw:drawing', END_DRAW: 'draw:end', + CLEAR_DRAW: 'draw:clear', BOUNDINGBOX_UPDATE: 'boundingbox:update', } as const); diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index 55499f5..c33bc52 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -12,6 +12,8 @@ import { } from '../../view/components/userQuery/types/canvas'; import { INeuralNetworkBase } from '../../core/types/NeuralNetworkBase'; import { IDataStore } from '../types/DataStore'; +import { Matrix2D } from '../../core/ops/types/OpsType.ts'; +import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; class QueryProcessController { private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; @@ -20,6 +22,8 @@ class QueryProcessController { private readonly resizeCanvas: ICanvasBase & IResizeCanvas; private readonly $NN: INeuralNetworkBase; private readonly $DS: IDataStore; + private readonly queryFrequencyMs: number; + //TODO: Canvas clear시 Skeleton 에니메이션 적용하기 constructor({ userInputCanvas, @@ -38,6 +42,7 @@ class QueryProcessController { this.$DS = $DS; this.registerDrawingEvent(); this.query(); + this.queryFrequencyMs = 200; } registerDrawingEvent(): void { @@ -57,13 +62,20 @@ class QueryProcessController { this.userInputCanvas.endPath(); this.trackingCanvas.endPath(); }); + eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => { + this.userInputCanvas.clear(); + this.trackingCanvas.clear(); + this.alignCanvas.clear(); + this.resizeCanvas.clear(); + BoundingBox.reset(); + }); } query() { const throttleQuery = throttle((inputs) => { - const result: number[] = this.$NN.query(inputs); + const result: Matrix2D = this.$NN.query(inputs); if (result) eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); - }, 100); + }, this.queryFrequencyMs); eventBus.on(DATA_EVENTS.QUERY_CHANGED, (inputs: number[]) => throttleQuery(inputs)); // TODO: type interface 추가하기, 이벤트버스 구조와 쿼리 구조 다시 생각해보기, TS 마이그레이션 From c73fbc6251e82c6f393a0d5974b2ded614bf45d2 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 16 Aug 2025 22:49:22 +0900 Subject: [PATCH 31/94] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=EB=90=9C=20type?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/types/DataStore.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts index e6b04f6..1d04382 100644 --- a/src/controller/types/DataStore.ts +++ b/src/controller/types/DataStore.ts @@ -1,3 +1,5 @@ +import { Matrix2D } from '../../core/ops/types/OpsType.ts'; + export interface IDataStore { setQueryInfo(queryInfo: number[]): void; getQueryInfo(): number[]; @@ -5,8 +7,8 @@ export interface IDataStore { setNodeState(nodeState: nodeState): void; getNodeState(): nodeState; - setQueryResult(queryResult: number[]): void; - getQueryResult(): number[]; + setQueryResult(queryResult: Matrix2D): void; + getQueryResult(): Matrix2D; } export interface nodeState { From c07e7483977165283ff720d62e02487a011e69a6 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 17 Aug 2025 00:22:48 +0900 Subject: [PATCH 32/94] =?UTF-8?q?chore:=20path=20alias=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jsconfig.json | 4 ++-- src/controller/AppController.ts | 18 +++++++++--------- src/controller/DataStore.ts | 2 +- .../queryPipeline/DrawingEventHandler.ts | 2 +- .../queryPipeline/QueryProcessController.ts | 8 ++++---- src/controller/queryPipeline/pixelExtractor.ts | 2 +- .../types/QueryProcessController.ts | 6 +++--- src/controller/types/DataStore.ts | 2 +- src/controller/types/eventBus.ts | 2 +- src/core/NeuralNetworkBase.ts | 6 +++--- src/core/WeightManager.ts | 4 ++-- src/core/types/NeuralNetworkBase.ts | 2 +- src/core/types/WeightManager.ts | 4 ++-- src/core/utils/loadPretrainedWeights.ts | 2 +- .../components/userQuery/PathTrackingCanvas.ts | 2 +- .../components/userQuery/UserInputCanvas.ts | 2 +- tsconfig.json | 6 +++++- vite.config.ts | 5 ++++- 18 files changed, 43 insertions(+), 36 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 0afe90a..1286948 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "baseUrl": "src", + "baseUrl": "./src", "paths": { - "@/*": ["./*"] + "@/*": ["*"] } }, "include": ["src"] diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 58a55ba..1d24875 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -1,20 +1,20 @@ -import WeightManager from '../core/WeightManager.ts'; -import NeuralNetworkBase from '../core/NeuralNetworkBase.ts'; +import WeightManager from '@/core/WeightManager.ts'; +import NeuralNetworkBase from '@/core/NeuralNetworkBase.ts'; import QueryProcessController from './queryPipeline/QueryProcessController.ts'; -import UserInputCanvas from '../view/components/userQuery/UserInputCanvas.ts'; -import PathTrackingCanvas from '../view/components/userQuery/PathTrackingCanvas.ts'; -import AlignCanvas from '../view/components/userQuery/AlignCanvas.ts'; -import ResizeCanvas from '../view/components/userQuery/ResizeCanvas.ts'; +import UserInputCanvas from '@/view/components/userQuery/UserInputCanvas.ts'; +import PathTrackingCanvas from '@/view/components/userQuery/PathTrackingCanvas.ts'; +import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts'; +import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts'; import DataStore from './DataStore.ts'; import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; -import { IWeightManager } from '../core/types/WeightManager.ts'; -import { INeuralNetworkBase } from '../core/types/NeuralNetworkBase.ts'; +import { IWeightManager } from '@/core/types/WeightManager.ts'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; -import { Matrix2D } from '../core/ops/types/OpsType.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; class AppController { private $WM: IWeightManager; diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts index 0b0d47c..556ab6c 100644 --- a/src/controller/DataStore.ts +++ b/src/controller/DataStore.ts @@ -2,7 +2,7 @@ import eventBus from './EventBus.js'; import { DATA_EVENTS } from './constants/events.js'; import { nodeState } from './types/DataStore'; -import { Matrix2D } from '../core/ops/types/OpsType.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; class DataStore { private queryInfo: number[] | null = null; diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index 44905ff..d64076c 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -1,4 +1,4 @@ -import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; +import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; import eventBus from '../EventBus.ts'; import { DRAWING_EVENTS } from '../constants/events.ts'; diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index c33bc52..eddbb2f 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -9,11 +9,11 @@ import { IPathTrackingCanvas, IAlignCanvas, IResizeCanvas, -} from '../../view/components/userQuery/types/canvas'; -import { INeuralNetworkBase } from '../../core/types/NeuralNetworkBase'; +} from '@/view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; import { IDataStore } from '../types/DataStore'; -import { Matrix2D } from '../../core/ops/types/OpsType.ts'; -import BoundingBox from '../../view/components/canvasUtils/BoundingBox.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; +import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; class QueryProcessController { private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; diff --git a/src/controller/queryPipeline/pixelExtractor.ts b/src/controller/queryPipeline/pixelExtractor.ts index 1f43ba4..3e37ef1 100644 --- a/src/controller/queryPipeline/pixelExtractor.ts +++ b/src/controller/queryPipeline/pixelExtractor.ts @@ -1,4 +1,4 @@ -import { IResizeCanvas, ICanvasBase } from '../../view/components/userQuery/types/canvas'; +import { IResizeCanvas, ICanvasBase } from '@/view/components/userQuery/types/canvas'; export const pixelExtractor = (path: IResizeCanvas & ICanvasBase) => { const pathToMatrix = (width: number, height: number, data: Uint8ClampedArray): object[] => { diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts index b549161..e43c0bb 100644 --- a/src/controller/queryPipeline/types/QueryProcessController.ts +++ b/src/controller/queryPipeline/types/QueryProcessController.ts @@ -4,9 +4,9 @@ import { IPathTrackingCanvas, IAlignCanvas, IResizeCanvas, -} from '../../../view/components/userQuery/types/canvas'; -import { INeuralNetworkBase } from '../../../core/types/NeuralNetworkBase'; -import { IDataStore } from '../../types/DataStore'; +} from '@/view/components/userQuery/types/canvas'; +import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; +import { IDataStore } from '@/controller/types/DataStore'; export interface IQueryProcessControllerProps { userInputCanvas: ICanvasBase & IUserInputCanvas; diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts index 1d04382..cff00ae 100644 --- a/src/controller/types/DataStore.ts +++ b/src/controller/types/DataStore.ts @@ -1,4 +1,4 @@ -import { Matrix2D } from '../../core/ops/types/OpsType.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; export interface IDataStore { setQueryInfo(queryInfo: number[]): void; diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts index 0697741..7c302ac 100644 --- a/src/controller/types/eventBus.ts +++ b/src/controller/types/eventBus.ts @@ -1,4 +1,4 @@ -import { Matrix2D } from '../../core/ops/types/OpsType'; +import { Matrix2D } from '@/core/ops/types/OpsType'; export interface eventPayloads { // Data handling event diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts index 9796c0f..3f1cc6e 100644 --- a/src/core/NeuralNetworkBase.ts +++ b/src/core/NeuralNetworkBase.ts @@ -1,9 +1,9 @@ import activationFunction from './ops/activationOps.js'; import { matrixMultiply } from './ops/matrixOps.js'; -import eventBus from '../controller/EventBus.js'; -import { DATA_EVENTS } from '../controller/constants/events.js'; +import eventBus from '@/controller/EventBus.js'; +import { DATA_EVENTS } from '@/controller/constants/events.js'; -import { weights } from '../controller/types/weights'; +import { weights } from '@/controller/types/weights'; import { Matrix2D } from './ops/types/OpsType'; class NeuralNetworkBase { diff --git a/src/core/WeightManager.ts b/src/core/WeightManager.ts index 1c42a93..29aa62d 100644 --- a/src/core/WeightManager.ts +++ b/src/core/WeightManager.ts @@ -1,8 +1,8 @@ import { createRandomWeight } from './utils/createRandomWeights.js'; import { loadPretrainedWeights } from './utils/loadPretrainedWeights.js'; -import { networkConfig } from '../controller/constants/types/networkConfig'; +import { networkConfig } from '@/controller/constants/types/networkConfig'; -import { weights } from '../controller/types/weights'; +import { weights } from '@/controller/types/weights'; class WeightManager { private _cache: null | weights = null; diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts index 98a66e9..0c15bab 100644 --- a/src/core/types/NeuralNetworkBase.ts +++ b/src/core/types/NeuralNetworkBase.ts @@ -1,4 +1,4 @@ -import { Matrix2D } from '../ops/types/OpsType.ts'; +import { Matrix2D } from '@/core/ops/types/OpsType.ts'; export interface INeuralNetworkBase { feedForward(inputs: number[]): { diff --git a/src/core/types/WeightManager.ts b/src/core/types/WeightManager.ts index 5473e62..5fd417f 100644 --- a/src/core/types/WeightManager.ts +++ b/src/core/types/WeightManager.ts @@ -1,5 +1,5 @@ -import { weights } from '../../controller/types/weights.ts'; -import { networkConfig } from '../../controller/constants/types/networkConfig.ts'; +import { weights } from '@/controller/types/weights.ts'; +import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; export interface IWeightManager { _cache: null | weights; diff --git a/src/core/utils/loadPretrainedWeights.ts b/src/core/utils/loadPretrainedWeights.ts index d10d0e1..3e89e03 100644 --- a/src/core/utils/loadPretrainedWeights.ts +++ b/src/core/utils/loadPretrainedWeights.ts @@ -1,4 +1,4 @@ -import { weights } from '../../controller/types/weights'; +import { weights } from '@/controller/types/weights'; export async function loadPretrainedWeights(path: string): Promise { const response: Response = await fetch(path); diff --git a/src/view/components/userQuery/PathTrackingCanvas.ts b/src/view/components/userQuery/PathTrackingCanvas.ts index 72a4e07..bd2b4b2 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.ts +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -1,4 +1,4 @@ -import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; +import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts'; class PathTrackingCanvas { public readonly canvas: HTMLCanvasElement | null; diff --git a/src/view/components/userQuery/UserInputCanvas.ts b/src/view/components/userQuery/UserInputCanvas.ts index 1c57361..564819a 100644 --- a/src/view/components/userQuery/UserInputCanvas.ts +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -1,4 +1,4 @@ -import { CANVAS_CONFIG } from '../../../controller/constants/canvasConfig.ts'; +import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts'; class UserInputCanvas { public readonly canvas: HTMLCanvasElement | null; diff --git a/tsconfig.json b/tsconfig.json index 592c17f..e5e1ea1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,11 @@ "strict": false, "allowJs": true, "checkJs": false, - "esModuleInterop": true + "esModuleInterop": true, + "baseUrl": "./src", + "paths": { + "@/*": ["*"] + } }, "include": ["src/**/*"] } diff --git a/vite.config.ts b/vite.config.ts index 58a9b50..1f6b4b0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,10 @@ import { defineConfig } from 'vite'; export default defineConfig({ - root: '.', // 기본값, 생략 가능 + root: './src', + resolve: { + alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], + }, publicDir: 'public', build: { outDir: 'dist', From 587ba48219c54b412d076d151d735f93f4101e99 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Fri, 29 Aug 2025 09:32:09 +0900 Subject: [PATCH 33/94] =?UTF-8?q?chore:=20alias=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=8C=8C=EC=9D=BC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 20 ++++++++++++++++++-- tsconfig.json | 3 ++- vite.config.ts | 6 +++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index eba51c4..2bacd31 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "private": true, "devDependencies": { + "@types/node": "^24.3.0", "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c498a0..e6b35da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@types/node': + specifier: ^24.3.0 + version: 24.3.0 eslint: specifier: ^9.32.0 version: 9.32.0 @@ -22,7 +25,7 @@ importers: version: 3.6.2 vite: specifier: ^7.0.6 - version: 7.0.6 + version: 7.0.6(@types/node@24.3.0) packages: @@ -350,6 +353,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -690,6 +696,9 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -949,6 +958,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -1304,11 +1317,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 + undici-types@7.10.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - vite@7.0.6: + vite@7.0.6(@types/node@24.3.0): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -1317,6 +1332,7 @@ snapshots: rollup: 4.46.2 tinyglobby: 0.2.14 optionalDependencies: + '@types/node': 24.3.0 fsevents: 2.3.3 which@2.0.2: diff --git a/tsconfig.json b/tsconfig.json index e5e1ea1..26f4f5c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "baseUrl": "./src", "paths": { "@/*": ["*"] - } + }, + "types": ["node"] }, "include": ["src/**/*"] } diff --git a/vite.config.ts b/vite.config.ts index 1f6b4b0..e53a7ea 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,11 @@ import { defineConfig } from 'vite'; +import { fileURLToPath } from 'url'; +import path from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); export default defineConfig({ - root: './src', resolve: { alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], }, From 680cc41b1cbf250246275889156130dfee132c21 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Fri, 29 Aug 2025 23:41:26 +0900 Subject: [PATCH 34/94] =?UTF-8?q?feat:=20perceptron=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EC=A0=81=EC=9A=A9(WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 11 +++++++ .../perceptron/EdgePositionHandler.ts | 1 + .../perceptron/NodePositionHandler.ts | 9 ++++++ .../perceptron/PerceptronController.ts | 29 +++++++++++++++++++ .../perceptron/ScrollEventHandler.ts | 1 + .../perceptron/types/PerceptronController.ts | 5 ++++ src/core/NeuralNetworkBase.ts | 7 ++--- src/view/components/perceptron/Edge.ts | 1 + src/view/components/perceptron/Node.ts | 8 +++++ .../perceptron/PerceptronRenderer.ts | 13 +++++++++ .../{Perceptron.ts => types/Edge.ts} | 0 src/view/components/perceptron/types/Node.ts | 12 ++++++++ 12 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/controller/perceptron/EdgePositionHandler.ts create mode 100644 src/controller/perceptron/NodePositionHandler.ts create mode 100644 src/controller/perceptron/PerceptronController.ts create mode 100644 src/controller/perceptron/ScrollEventHandler.ts create mode 100644 src/controller/perceptron/types/PerceptronController.ts create mode 100644 src/view/components/perceptron/Edge.ts create mode 100644 src/view/components/perceptron/Node.ts create mode 100644 src/view/components/perceptron/PerceptronRenderer.ts rename src/view/components/perceptron/{Perceptron.ts => types/Edge.ts} (100%) create mode 100644 src/view/components/perceptron/types/Node.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 1d24875..db312a9 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -1,12 +1,15 @@ import WeightManager from '@/core/WeightManager.ts'; import NeuralNetworkBase from '@/core/NeuralNetworkBase.ts'; import QueryProcessController from './queryPipeline/QueryProcessController.ts'; +import PerceptronController from '@/controller/perceptron/PerceptronController.ts'; import UserInputCanvas from '@/view/components/userQuery/UserInputCanvas.ts'; import PathTrackingCanvas from '@/view/components/userQuery/PathTrackingCanvas.ts'; import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts'; import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts'; import DataStore from './DataStore.ts'; +import PerceptronController from '@/controller/perceptron/PerceptronController.ts'; + import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; @@ -15,6 +18,9 @@ import { IWeightManager } from '@/core/types/WeightManager.ts'; import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; +import { NodePositionHandler } from '@/controller/perceptron/NodePositionHandler.ts'; +import { EdgePositionHandler } from '@/controller/perceptron/EdgePositionHandler.ts'; +import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { private $WM: IWeightManager; @@ -34,6 +40,11 @@ class AppController { $NN: $NN, $DS: $DS, }); + const $PC = new PerceptronController({ + nodePositionHandler: new NodePositionHandler(NETWORK_CONFIG), + edgePositionHandler: new EdgePositionHandler(), + scrollEventHandler: new ScrollEventHandler(), + }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { console.log('RESULT CHANGED', data); }); diff --git a/src/controller/perceptron/EdgePositionHandler.ts b/src/controller/perceptron/EdgePositionHandler.ts new file mode 100644 index 0000000..8ab1b00 --- /dev/null +++ b/src/controller/perceptron/EdgePositionHandler.ts @@ -0,0 +1 @@ +export class EdgePositionHandler {} diff --git a/src/controller/perceptron/NodePositionHandler.ts b/src/controller/perceptron/NodePositionHandler.ts new file mode 100644 index 0000000..f0181b3 --- /dev/null +++ b/src/controller/perceptron/NodePositionHandler.ts @@ -0,0 +1,9 @@ +import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; + +export class NodePositionHandler { + private networkConfig: networkConfig; + + constructor(networkConfig: networkConfig) { + this.networkConfig = networkConfig; + } +} diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts new file mode 100644 index 0000000..4f013c2 --- /dev/null +++ b/src/controller/perceptron/PerceptronController.ts @@ -0,0 +1,29 @@ +import { IPerceptronControllerProps } from '@/controller/perceptron/types/PerceptronController.ts'; +import { NodePositionHandler } from '@/controller/perceptron/NodePositionHandler.ts'; +import { EdgePositionHandler } from '@/controller/perceptron/EdgePositionHandler.ts'; +import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; +import { DATA_EVENTS, DataEvent } from '@/controller/constants/events.ts'; +import eventBus from '@/controller/EventBus.ts'; +import { eventPayloads } from '@/controller/types/eventBus.ts'; +import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; + +class PerceptronController { + private nodePositionHandler: NodePositionHandler; + private edgePositionHandler: EdgePositionHandler; + private scrollEventHandler: ScrollEventHandler; + + constructor({ + nodePositionHandler, + edgePositionHandler, + scrollEventHandler, + }: IPerceptronControllerProps) { + this.nodePositionHandler = nodePositionHandler; + this.edgePositionHandler = edgePositionHandler; + this.scrollEventHandler = scrollEventHandler; + eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { + console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); + }); + } +} + +export default PerceptronController; diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts new file mode 100644 index 0000000..47a50e2 --- /dev/null +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -0,0 +1 @@ +export class ScrollEventHandler {} diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts new file mode 100644 index 0000000..7eed8a9 --- /dev/null +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -0,0 +1,5 @@ +export interface IPerceptronControllerProps { + nodePositionHandler: INodePositionHandler; + edgePositionHandler: INdgePositionHandler; + scrollEventHandler: IScrollEventHandler; +} diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts index 3f1cc6e..a5a2c67 100644 --- a/src/core/NeuralNetworkBase.ts +++ b/src/core/NeuralNetworkBase.ts @@ -18,7 +18,6 @@ class NeuralNetworkBase { // CNN operations feedForward(inputs: number[]): { - hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D; } { @@ -29,12 +28,12 @@ class NeuralNetworkBase { const hiddenOutputs: Matrix2D = activationFunction(hiddenInputs); const finalInputs: Matrix2D = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs); const finalOutputs: Matrix2D = activationFunction(finalInputs); - return { hiddenInputs, hiddenOutputs, finalOutputs }; + return { hiddenOutputs, finalOutputs }; } query(inputs: number[]): Matrix2D { - const { hiddenInputs, hiddenOutputs, finalOutputs } = this.feedForward(inputs); - eventBus.emit(DATA_EVENTS.NODE_UPDATE, { hiddenInputs, hiddenOutputs, finalOutputs }); + const { hiddenOutputs, finalOutputs } = this.feedForward(inputs); + eventBus.emit(DATA_EVENTS.NODE_CHANGED, { inputs, hiddenOutputs, finalOutputs }); return finalOutputs; } } diff --git a/src/view/components/perceptron/Edge.ts b/src/view/components/perceptron/Edge.ts new file mode 100644 index 0000000..b2ffa42 --- /dev/null +++ b/src/view/components/perceptron/Edge.ts @@ -0,0 +1 @@ +class Edge {} diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/Node.ts new file mode 100644 index 0000000..3e34c5c --- /dev/null +++ b/src/view/components/perceptron/Node.ts @@ -0,0 +1,8 @@ +import { layer, index, Position, edgePosition } from '@/view/components/perceptron/types/Node.ts'; + +export class Node { + private layer: layer; + private index: index; + private Position: Position; + private edgePosition: edgePosition; +} diff --git a/src/view/components/perceptron/PerceptronRenderer.ts b/src/view/components/perceptron/PerceptronRenderer.ts new file mode 100644 index 0000000..3d9b892 --- /dev/null +++ b/src/view/components/perceptron/PerceptronRenderer.ts @@ -0,0 +1,13 @@ +import eventBus from '@/controller/EventBus.ts'; + +class PerceptronRenderer { + 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 }); + } + + updateRender(): void {} +} diff --git a/src/view/components/perceptron/Perceptron.ts b/src/view/components/perceptron/types/Edge.ts similarity index 100% rename from src/view/components/perceptron/Perceptron.ts rename to src/view/components/perceptron/types/Edge.ts diff --git a/src/view/components/perceptron/types/Node.ts b/src/view/components/perceptron/types/Node.ts new file mode 100644 index 0000000..85afdf6 --- /dev/null +++ b/src/view/components/perceptron/types/Node.ts @@ -0,0 +1,12 @@ +export type layer = string; +export type index = number; + +export interface Position { + x: number; + y: number; +} + +export interface edgePosition { + x: number; + y: number; +} From 49c983442761dbdf86c89848a23035a7b44ffa92 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Fri, 29 Aug 2025 23:43:18 +0900 Subject: [PATCH 35/94] =?UTF-8?q?fix:=20=EC=9E=98=EB=AA=BB=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=EB=90=9C=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - perceptron상에 input layer array가 hidden layer input으로 표기되던 문제 수정 - 'node:update' 를 'node:chaged'로 수정 --- src/controller/types/eventBus.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts index 7c302ac..1ad44ac 100644 --- a/src/controller/types/eventBus.ts +++ b/src/controller/types/eventBus.ts @@ -3,7 +3,7 @@ import { Matrix2D } from '@/core/ops/types/OpsType'; export interface eventPayloads { // Data handling event 'query:changed': number[]; - 'node:changed': { inputNodes: number[]; hiddenNodes: number[]; outputNodes: number[] }; + 'node:changed': { inputs: number[]; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; 'result:changed': Matrix2D; // Canvas handling event @@ -13,7 +13,7 @@ export interface eventPayloads { 'draw:clear': void; 'bondingbox:update': { X: number; y: number }; - 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; + // 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; } export interface IEventBus { From 2877545dd387ec36606d97d681c89be4d629d1f8 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Fri, 29 Aug 2025 23:44:34 +0900 Subject: [PATCH 36/94] =?UTF-8?q?feat:=20=EC=9A=A9=EC=9D=B4=ED=95=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=ED=99=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20inp?= =?UTF-8?q?ut=EA=B0=92=20log=20scale=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/utils/compressArr.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/controller/perceptron/utils/compressArr.ts diff --git a/src/controller/perceptron/utils/compressArr.ts b/src/controller/perceptron/utils/compressArr.ts new file mode 100644 index 0000000..273493f --- /dev/null +++ b/src/controller/perceptron/utils/compressArr.ts @@ -0,0 +1,9 @@ +export const compressArr = (arr: number[]): number[] => { + const resized: number[] = arr.reduce((acc: number[], cur: number, idx: number) => { + if (idx % 8 === 0) acc.push(cur); + else acc[acc.length - 1] += cur; + return acc; + }, []); + const logScale: number[] = resized.map((v: number): number => Math.log(v + 1) / Math.log(9)); + return logScale; +}; From c8cdb7a92957ed984ed54bc303bdd997d615505c Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:26:11 +0900 Subject: [PATCH 37/94] =?UTF-8?q?chore:=20id=EC=9D=98=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9D=84=20kebab-case=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 3e0130a..b88ec5e 100644 --- a/index.html +++ b/index.html @@ -9,20 +9,23 @@ +
-
+
-
- -
-
- - -
+
+
+ +
+
+ + +
+
\ No newline at end of file From cb7497d85fd541f88c06466a165bcc7d48c9ed78 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:26:44 +0900 Subject: [PATCH 38/94] =?UTF-8?q?refactor:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B3=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/rendererConfig.ts | 9 ++++ .../constants/types/rendererConfig.ts | 7 +++ ...{EdgePositionHandler.ts => EdgeHandler.ts} | 0 src/controller/perceptron/NodeHandler.ts | 41 ++++++++++++++++ src/view/components/perceptron/BaseCanvas.ts | 49 +++++++++++++++++++ .../components/perceptron/NodeRenderer.ts | 34 +++++++++++++ 6 files changed, 140 insertions(+) create mode 100644 src/controller/constants/rendererConfig.ts create mode 100644 src/controller/constants/types/rendererConfig.ts rename src/controller/perceptron/{EdgePositionHandler.ts => EdgeHandler.ts} (100%) create mode 100644 src/controller/perceptron/NodeHandler.ts create mode 100644 src/view/components/perceptron/BaseCanvas.ts create mode 100644 src/view/components/perceptron/NodeRenderer.ts diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts new file mode 100644 index 0000000..3843e9d --- /dev/null +++ b/src/controller/constants/rendererConfig.ts @@ -0,0 +1,9 @@ +import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; + +export const RENDERER_CONFIG: rendererConfig = { + rotationDelta: 4, + degree: Math.PI / 180, + mouseScroll: 1, + displayNodes: 30, + scrollSpeed: 4, +}; diff --git a/src/controller/constants/types/rendererConfig.ts b/src/controller/constants/types/rendererConfig.ts new file mode 100644 index 0000000..e5dc361 --- /dev/null +++ b/src/controller/constants/types/rendererConfig.ts @@ -0,0 +1,7 @@ +export interface rendererConfig { + rotationDelta: number; + degree: number; + mouseScroll: number; + displayNodes: number; + scrollSpeed: number; +} diff --git a/src/controller/perceptron/EdgePositionHandler.ts b/src/controller/perceptron/EdgeHandler.ts similarity index 100% rename from src/controller/perceptron/EdgePositionHandler.ts rename to src/controller/perceptron/EdgeHandler.ts diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts new file mode 100644 index 0000000..40bccd5 --- /dev/null +++ b/src/controller/perceptron/NodeHandler.ts @@ -0,0 +1,41 @@ +import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; +import { Node } from '@/view/components/perceptron/Node.ts'; + +export class NodeHandler { + private readonly nodeObjectSet: { + inputNodes: Array; + hiddenNodes: Array; + outputNodes: Array; + }; + private displayNodes: number; + private nodeRenderer: INodeRenderer; + + constructor({ + networkConfig, + nodeRenderer, + }: { + networkConfig: networkConfig; + nodeRenderer: INodeRenderer; + }) { + this.nodeObjectSet = { + inputNodes: undefined, + hiddenNodes: undefined, + outputNodes: undefined, + }; + this.nodeRenderer = nodeRenderer; + + this.initialize(networkConfig); + console.log(this.nodeObjectSet); + } + + initialize(networkConfig: networkConfig): void { + Object.keys(this.nodeObjectSet).forEach((key: string) => { + this.nodeObjectSet[key] = Array.from({ length: networkConfig[key] }, () => { + return new Node(0); + }); + }); + this.renderNodes(); + } + + render() {} +} diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts new file mode 100644 index 0000000..fed87c4 --- /dev/null +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -0,0 +1,49 @@ +import eventBus from '@/controller/EventBus.ts'; +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +import { rendererConfig } from '@/controller/constants/types/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 }; + + constructor() { + this.canvas = document.getElementById('perceptron') as HTMLCanvasElement | null; + this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + + this.setupCanvas(); // canvas element setu + this.setupRenderTransform(this.ctx, this.nodes, this.degree, this.rotationDelta); // apply state for rendering context(transform/rotate..) + } + + getCtx = (): CanvasRenderingContext2D => this.ctx; + + setupCanvas() { + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight * (5 / 20); + + this.ctx.fillStyle = 'rgb(186,186,186)'; + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.canvasCenter = { x: this.canvas.width / 2, y: this.canvas.height / 2 }; + } + + setupRenderTransform( + ctx: CanvasRenderingContext2D, + nodes: number, + degree: number, + rotationDelta: number, + ): void { + ctx.translate(this.canvasCenter.x, this.canvasCenter.y); + ctx.rotate(degree * 180); + ctx.rotate((degree * 180 - degree * rotationDelta * nodes) / 2); + } + + clearCanvas() { + 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.ctx.translate(this.canvasCenter.x, this.canvasCenter.y); + } +} + +export default BaseCanvas; diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts new file mode 100644 index 0000000..622e597 --- /dev/null +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -0,0 +1,34 @@ +import { rendererConfig } from '@/controller/constants/types/rendererConfig.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.scrollSpeed; // scrollHandler + this.mouseScroll = 0; // scrollHandler + this.touchMove = 0; // scrollHandler + this.touchDirection = 0; // scrollHandler + } + + renderNodes() {} +} + +export default nodeRenderer; From ae4360fb9ef26d616e5a89c23ec66c8cc2fe1998 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:28:17 +0900 Subject: [PATCH 39/94] =?UTF-8?q?refactor:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B3=90=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 17 +++++++++++++---- src/controller/constants/networkConfig.ts | 1 - src/controller/constants/types/networkConfig.ts | 1 - .../perceptron/NodePositionHandler.ts | 9 --------- .../perceptron/PerceptronController.ts | 9 ++++++--- .../perceptron/types/PerceptronController.ts | 1 + src/view/components/perceptron/Node.ts | 16 +++++++++++----- src/view/components/perceptron/types/Node.ts | 13 +------------ 8 files changed, 32 insertions(+), 35 deletions(-) delete mode 100644 src/controller/perceptron/NodePositionHandler.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index db312a9..8691615 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -2,13 +2,14 @@ import WeightManager from '@/core/WeightManager.ts'; import NeuralNetworkBase from '@/core/NeuralNetworkBase.ts'; import QueryProcessController from './queryPipeline/QueryProcessController.ts'; import PerceptronController from '@/controller/perceptron/PerceptronController.ts'; +import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; import UserInputCanvas from '@/view/components/userQuery/UserInputCanvas.ts'; import PathTrackingCanvas from '@/view/components/userQuery/PathTrackingCanvas.ts'; import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts'; import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts'; import DataStore from './DataStore.ts'; -import PerceptronController from '@/controller/perceptron/PerceptronController.ts'; +import NodeRenderer from '@/view/components/perceptron/NodeRenderer.ts'; import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; @@ -18,8 +19,8 @@ import { IWeightManager } from '@/core/types/WeightManager.ts'; import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; -import { NodePositionHandler } from '@/controller/perceptron/NodePositionHandler.ts'; -import { EdgePositionHandler } from '@/controller/perceptron/EdgePositionHandler.ts'; +import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; +import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { @@ -40,8 +41,16 @@ class AppController { $NN: $NN, $DS: $DS, }); + + const perceptronBaseCanvas = new BaseCanvas(); + const nodeRenderer = new NodeRenderer(perceptronBaseCanvas.getCtx()); + const nodeHandler = new NodeHandler({ + networkConfig: NETWORK_CONFIG, + nodeRenderer, + }); const $PC = new PerceptronController({ - nodePositionHandler: new NodePositionHandler(NETWORK_CONFIG), + nodeRenderer: nodeRenderer, + nodePositionHandler: nodeHandler, edgePositionHandler: new EdgePositionHandler(), scrollEventHandler: new ScrollEventHandler(), }); diff --git a/src/controller/constants/networkConfig.ts b/src/controller/constants/networkConfig.ts index fe022fd..e759116 100644 --- a/src/controller/constants/networkConfig.ts +++ b/src/controller/constants/networkConfig.ts @@ -4,5 +4,4 @@ export const NETWORK_CONFIG: networkConfig = { inputNodes: 784, hiddenNodes: 200, outputNodes: 10, - learningRate: 0.15, }; diff --git a/src/controller/constants/types/networkConfig.ts b/src/controller/constants/types/networkConfig.ts index f099979..5baddcd 100644 --- a/src/controller/constants/types/networkConfig.ts +++ b/src/controller/constants/types/networkConfig.ts @@ -2,5 +2,4 @@ export interface networkConfig { inputNodes: number; hiddenNodes: number; outputNodes: number; - learningRate: number; } diff --git a/src/controller/perceptron/NodePositionHandler.ts b/src/controller/perceptron/NodePositionHandler.ts deleted file mode 100644 index f0181b3..0000000 --- a/src/controller/perceptron/NodePositionHandler.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; - -export class NodePositionHandler { - private networkConfig: networkConfig; - - constructor(networkConfig: networkConfig) { - this.networkConfig = networkConfig; - } -} diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 4f013c2..cf1817c 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -1,18 +1,21 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/PerceptronController.ts'; -import { NodePositionHandler } from '@/controller/perceptron/NodePositionHandler.ts'; -import { EdgePositionHandler } from '@/controller/perceptron/EdgePositionHandler.ts'; +import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; +import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; import { DATA_EVENTS, DataEvent } from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; +import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; + class PerceptronController { - private nodePositionHandler: NodePositionHandler; + private nodePositionHandler: NodeHandler; private edgePositionHandler: EdgePositionHandler; private scrollEventHandler: ScrollEventHandler; constructor({ + nodeRenderer, nodePositionHandler, edgePositionHandler, scrollEventHandler, diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index 7eed8a9..300d3d8 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -1,5 +1,6 @@ export interface IPerceptronControllerProps { nodePositionHandler: INodePositionHandler; edgePositionHandler: INdgePositionHandler; + nodeRenderer: INodeRenderer; scrollEventHandler: IScrollEventHandler; } diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/Node.ts index 3e34c5c..56654dd 100644 --- a/src/view/components/perceptron/Node.ts +++ b/src/view/components/perceptron/Node.ts @@ -1,8 +1,14 @@ -import { layer, index, Position, edgePosition } from '@/view/components/perceptron/types/Node.ts'; +import { value } from '@/view/components/perceptron/types/Node.ts'; export class Node { - private layer: layer; - private index: index; - private Position: Position; - private edgePosition: edgePosition; + private value: value; + constructor(value: value) { + this.value = value; + } + setValue(value: value) { + this.value = value; + } + getValue(value: value) { + this.value = value; + } } diff --git a/src/view/components/perceptron/types/Node.ts b/src/view/components/perceptron/types/Node.ts index 85afdf6..3fd8de0 100644 --- a/src/view/components/perceptron/types/Node.ts +++ b/src/view/components/perceptron/types/Node.ts @@ -1,12 +1 @@ -export type layer = string; -export type index = number; - -export interface Position { - x: number; - y: number; -} - -export interface edgePosition { - x: number; - y: number; -} +export type value = number; From acf27fa2845ee5264ac765f3d1ee5afd709e2ba7 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:29:03 +0900 Subject: [PATCH 40/94] =?UTF-8?q?chore:=20kebab-case=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?getElementById=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/queryPipeline/DrawingEventHandler.ts | 2 +- src/view/components/userQuery/UserInputCanvas.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index d64076c..70e2507 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -8,7 +8,7 @@ class DrawingEventHandler { private isDrawing: boolean; constructor() { - this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; + this.inputCanvas = document.getElementById('user-input-canvas') as HTMLCanvasElement; this.clearButton = document.getElementById('clear') as HTMLButtonElement; this.isDrawing = false; this.registerEvents(); diff --git a/src/view/components/userQuery/UserInputCanvas.ts b/src/view/components/userQuery/UserInputCanvas.ts index 564819a..4e80483 100644 --- a/src/view/components/userQuery/UserInputCanvas.ts +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -6,7 +6,7 @@ class UserInputCanvas { private isDrawing: boolean; constructor() { - this.canvas = document.getElementById('userInputCanvas') as HTMLCanvasElement; + this.canvas = document.getElementById('user-input-canvas') as HTMLCanvasElement; this.ctx = this.canvas.getContext('2d'); this.isDrawing = false; this.setupCanvas(); @@ -25,7 +25,7 @@ class UserInputCanvas { setupCanvas(): void { this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight / 3; + this.canvas.height = window.innerHeight * (7 / 20); this.ctx.fillStyle = 'rgba(40,40,40)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); From c1b4344c1a9132bc1ffc13134a02ac8091c17090 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:30:05 +0900 Subject: [PATCH 41/94] =?UTF-8?q?feat:=20node=20render=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20shapeVector=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../perceptron/shapeVector/nodePath.ts | 49 +++++++++++++++++++ .../perceptron/shapeVector/types/nodePath.ts | 8 +++ 2 files changed, 57 insertions(+) create mode 100644 src/view/components/perceptron/shapeVector/nodePath.ts create mode 100644 src/view/components/perceptron/shapeVector/types/nodePath.ts diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts new file mode 100644 index 0000000..511a502 --- /dev/null +++ b/src/view/components/perceptron/shapeVector/nodePath.ts @@ -0,0 +1,49 @@ +import { INodePath } from '@/view/components/perceptron/shape/types/nodePath.ts'; + +export const nodePath = ( + ctx: CanvasRenderingContext2D, + { x, y, width, height, radius = 8, percent }: INodePath, +) => { + const node: Path2D = roundedRect(x, y, width, height, radius); + + ctx.fillStyle = '#fff'; + ctx.strokeStyle = '#000'; + ctx.lineWidth = 1; + ctx.fill(node); + ctx.stroke(node); + + if (percent > 0) { + const filledHeight = (height * percent) / 100; + const fillY = y + height - filledHeight; + + const fillPath = roundedRect(x, fillY, width, filledHeight, radius); + ctx.save(); + ctx.clip(node); + ctx.fillStyle = '#000'; + ctx.fill(fillPath); + 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; +} From 26ac970453f40f5214736c853668f4382003bbf2 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:30:36 +0900 Subject: [PATCH 42/94] =?UTF-8?q?chore:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B3=90=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/types/NeuralNetworkBase.ts | 1 - .../components/perceptron/PerceptronRenderer.ts | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 src/view/components/perceptron/PerceptronRenderer.ts diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts index 0c15bab..399a911 100644 --- a/src/core/types/NeuralNetworkBase.ts +++ b/src/core/types/NeuralNetworkBase.ts @@ -2,7 +2,6 @@ import { Matrix2D } from '@/core/ops/types/OpsType.ts'; export interface INeuralNetworkBase { feedForward(inputs: number[]): { - hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D; }; diff --git a/src/view/components/perceptron/PerceptronRenderer.ts b/src/view/components/perceptron/PerceptronRenderer.ts deleted file mode 100644 index 3d9b892..0000000 --- a/src/view/components/perceptron/PerceptronRenderer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import eventBus from '@/controller/EventBus.ts'; - -class PerceptronRenderer { - 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 }); - } - - updateRender(): void {} -} From a34ee4e0ad81f409fef5c424c2adddfe638f4ab3 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:32:11 +0900 Subject: [PATCH 43/94] =?UTF-8?q?design:=20userInputCanvas=EC=9D=98=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=B9=84=EC=9C=A8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/components/userQuery/PathTrackingCanvas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/components/userQuery/PathTrackingCanvas.ts b/src/view/components/userQuery/PathTrackingCanvas.ts index bd2b4b2..5111c4c 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.ts +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -12,7 +12,7 @@ class PathTrackingCanvas { setupCanvas(): void { this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight / 3; + this.canvas.height = window.innerHeight * (7 / 20); this.ctx.fillStyle = 'rgba(0, 0, 0)'; this.ctx.strokeStyle = 'rgba(255, 255, 255)'; From 3b6a1815884650fea542a2183c40c5e93e044eb2 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 1 Oct 2025 14:32:47 +0900 Subject: [PATCH 44/94] =?UTF-8?q?feat:=20userInputCanvas=EC=9D=98=20curve?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/styles/main.css | 45 +++++++++++++++++++++++++++++++++++- src/view/styles/settings.css | 4 ++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/view/styles/main.css b/src/view/styles/main.css index b02f871..4bf025c 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -1,3 +1,46 @@ #canvas { border: 1px solid black; -} \ No newline at end of file +} +#app { + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +#canvas-unit { + position: absolute; + bottom: 0; + left: 0; +} + +#user-input-section { + position: relative; + /*display: flex;*/ + /*justify-content: end;*/ +} + +#user-input-canvas { + position: relative; + z-index: 1; + clip-path: ellipse(140% 100% at 50% 100%); +} +#user-input-section > #clear { + position: absolute; + z-index: 2; + + top: 30px; + right: 10px +} + +#perceptron-section { + position: relative; + bottom: -45px; + left: 0; + + width: 100%; + #perceptron { + width: 100%; + height: 100%; + } +} diff --git a/src/view/styles/settings.css b/src/view/styles/settings.css index 45f760e..9650093 100644 --- a/src/view/styles/settings.css +++ b/src/view/styles/settings.css @@ -2,4 +2,8 @@ margin: 0; padding: 0; box-sizing: border-box; +} + +canvas { + display: block; } \ No newline at end of file From 3f2d2f8eb38af902571ddbfc60e009da0722baaa Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 7 Oct 2025 10:58:29 +0900 Subject: [PATCH 45/94] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B2=BD=EB=A7=9D=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=81=ED=99=94=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/events.ts | 5 ++ src/controller/constants/rendererConfig.ts | 2 +- .../constants/types/rendererConfig.ts | 2 +- src/controller/perceptron/NodeHandler.ts | 64 ++++++++++++++- .../perceptron/ScrollEventHandler.ts | 77 ++++++++++++++++++- 5 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/controller/constants/events.ts b/src/controller/constants/events.ts index bf0e08c..84b6cf1 100644 --- a/src/controller/constants/events.ts +++ b/src/controller/constants/events.ts @@ -13,6 +13,11 @@ export const HANDLER_EVENTS = Object.freeze({ ICH_INITIALIZE: 'ich:initialize', } as const); +export const SCROLL_EVENTS = Object.freeze({ + SCROLL_CHANGED: 'scroll:changed', + TOUCH_CHANGED: 'touch:changed', +}); + export const DRAWING_EVENTS = Object.freeze({ START_DRAW: 'draw:start', DRAW: 'draw:drawing', diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts index 3843e9d..98c1722 100644 --- a/src/controller/constants/rendererConfig.ts +++ b/src/controller/constants/rendererConfig.ts @@ -5,5 +5,5 @@ export const RENDERER_CONFIG: rendererConfig = { degree: Math.PI / 180, mouseScroll: 1, displayNodes: 30, - scrollSpeed: 4, + scrollDivider: 4, }; diff --git a/src/controller/constants/types/rendererConfig.ts b/src/controller/constants/types/rendererConfig.ts index e5dc361..06cfec4 100644 --- a/src/controller/constants/types/rendererConfig.ts +++ b/src/controller/constants/types/rendererConfig.ts @@ -3,5 +3,5 @@ export interface rendererConfig { degree: number; mouseScroll: number; displayNodes: number; - scrollSpeed: number; + scrollDivider: number; } diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 40bccd5..738f727 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -1,5 +1,10 @@ import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; import { Node } from '@/view/components/perceptron/Node.ts'; +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; + +import eventBus from '@/controller/eventBus.ts'; +import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; export class NodeHandler { private readonly nodeObjectSet: { @@ -9,13 +14,16 @@ export class NodeHandler { }; private displayNodes: number; private nodeRenderer: INodeRenderer; + private BaseCanvas: IBaseCanvas; constructor({ networkConfig, nodeRenderer, + perceptronBaseCanvas, }: { networkConfig: networkConfig; nodeRenderer: INodeRenderer; + perceptronBaseCanvas: IBaseCanvas; }) { this.nodeObjectSet = { inputNodes: undefined, @@ -23,19 +31,69 @@ export class NodeHandler { outputNodes: undefined, }; this.nodeRenderer = nodeRenderer; + this.BaseCanvas = perceptronBaseCanvas; this.initialize(networkConfig); + this.registerScrollEvent(); + console.log(this.nodeObjectSet); } initialize(networkConfig: networkConfig): void { + const { inputNodes, ...rest } = networkConfig; + const normalizedNetworkConfig = { inputNodes: inputNodes / 8, ...rest }; // 원활한 시각화를 위해 입력 레이어의 크기를 compress Object.keys(this.nodeObjectSet).forEach((key: string) => { - this.nodeObjectSet[key] = Array.from({ length: networkConfig[key] }, () => { + this.nodeObjectSet[key] = Array.from({ length: normalizedNetworkConfig[key] }, () => { return new Node(0); }); }); - this.renderNodes(); + this.render(0, 0); } - render() {} + registerScrollEvent() { + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => this.render(mouseScroll, 0)); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => this.render(0, touchScroll)); + } + + render(mouseScroll: number, touchScroll: number): void { + // Object.keys(this.nodeObjectSet).forEach((key: string) => { + // console.log('key:', key, 'value:', this.nodeObjectSet[key]); + // }); + const scroll = mouseScroll; + const touch = touchScroll; + + Object.keys(this.nodeObjectSet).forEach((key: string) => { + this.BaseCanvas.rotateCanvas( + true, + (RENDERER_CONFIG.degree * 180 - + RENDERER_CONFIG.degree * + RENDERER_CONFIG.rotationDelta * + Object.keys(this.nodeObjectSet).length) / + 2, + ); + const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 + Array.from({ length: layerSize }, (_: unknown, i: number) => { + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + return i; + }) + .filter( + (i: number) => + i >= + layerSize / 2 - + displayNodes / 2 + + (mouseScroll + touchScroll) / scrollDivider && + i < + layerSize / 2 + + displayNodes / 2 + + (mouseScroll + touchScroll) / scrollDivider, + ) + .forEach((i) => { + console.log('돌아가고 있니..?'); + this.nodeRenderer.drawNode(this.nodeObjectSet[key][i].getValue()); + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + }); + }); + + // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); + } } diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index 47a50e2..ab6b791 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -1 +1,76 @@ -export class ScrollEventHandler {} +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; + +import eventBus from '@/controller/EventBus.ts'; +import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; + +const { degree, rotationDelta, scrollDivider } = RENDERER_CONFIG; + +class ScrollEventHandler { + private BaseCanvas: IBaseCanvas; + private readonly canvasEl: HTMLCanvasElement; + + private mouseScroll: number; + private touchMove: number; + private touchDirection: number; + private rotationStack: number; + + constructor(BaseCanvas: IBaseCanvas) { + this.BaseCanvas = BaseCanvas; + this.canvasEl = BaseCanvas.getCanvas(); // perceptron base canvas + + this.mouseScroll = 0; + this.touchMove = 0; + this.touchDirection = 0; + + this.registerEvent(); + } + + registerEvent() { + this.handleMouseScroll(); + this.handleTouchMove(); + } + + handleMouseScroll(): void { + this.canvasEl.addEventListener('wheel', (e: WheelEvent) => { + if (e.deltaY > 0) { + this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / scrollDivider)); + this.mouseScroll += 1; + this.rotationStack += 1; + eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); + } else if (e.deltaY < 0) { + this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / scrollDivider)); + this.mouseScroll -= 1; + this.rotationStack -= 1; + eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); + } + }); + } + handleTouchMove(): void { + let touchStartX = 0; + this.canvasEl.addEventListener('touchstart', (e: TouchEvent) => { + const touch = e.touches[0]; + touchStartX = Math.floor(touch.clientX); + }); + this.canvasEl.addEventListener('touchmove', (e: TouchEvent) => { + const touch = e.touches[0]; + const moveX = Math.floor(touch.clientX); + this.touchDirection = moveX - touchStartX; + + if (this.touchDirection < 0) { + this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / scrollDivider)); + this.touchMove += 1; + this.rotationStack += 1; + eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); + } else if (this.touchDirection > 0) { + this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / scrollDivider)); + this.touchMove -= 1; + this.rotationStack -= 1; + eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove0); + } + }); + } + + getMouseScroll = () => this.mouseScroll; +} + +export default ScrollEventHandler; From d6b29e669647f90e340fccc6ae0aba73aa0df380 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 7 Oct 2025 10:59:24 +0900 Subject: [PATCH 46/94] =?UTF-8?q?chore:=20=EC=8B=9C=EA=B0=81=ED=99=94=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20payload=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/types/eventBus.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts index 1ad44ac..3939a2c 100644 --- a/src/controller/types/eventBus.ts +++ b/src/controller/types/eventBus.ts @@ -13,6 +13,9 @@ export interface eventPayloads { 'draw:clear': void; 'bondingbox:update': { X: number; y: number }; + 'scroll:changed': number; + 'touch:changed': number; + // 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D }; } From ab37166e86763d93d4e32e53fcae75d654ae0572 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 7 Oct 2025 11:00:24 +0900 Subject: [PATCH 47/94] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=B3=80=EC=88=98=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/components/perceptron/Node.ts | 4 ++-- src/view/components/perceptron/NodeRenderer.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/Node.ts index 56654dd..46a0c7f 100644 --- a/src/view/components/perceptron/Node.ts +++ b/src/view/components/perceptron/Node.ts @@ -5,10 +5,10 @@ export class Node { constructor(value: value) { this.value = value; } - setValue(value: value) { + setValue(value: value): void { this.value = value; } - getValue(value: value) { + getValue(value: value): number { this.value = value; } } diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index 622e597..8190a83 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -1,4 +1,5 @@ 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 }; @@ -22,13 +23,13 @@ class nodeRenderer { this.rotationDelta = RENDERER_CONFIG.rotationDelta; // scrollHandler this.degree = RENDERER_CONFIG.degree; // scrollHandler this.displayNodes = RENDERER_CONFIG.displayNodes; // nodeHandler - this.scrollSpeed = RENDERER_CONFIG.scrollSpeed; // scrollHandler - this.mouseScroll = 0; // scrollHandler - this.touchMove = 0; // scrollHandler - this.touchDirection = 0; // scrollHandler + this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler } - renderNodes() {} + drawNode = (p: number): void => { + console.log('그려짐.'); + nodePath(this.renderCtx, { x: 200, y: 0, width: 10, height: 10, percent: p }); + }; } export default nodeRenderer; From ffb1649726892d083c840e4cf86aa529f81f061d Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 7 Oct 2025 11:01:51 +0900 Subject: [PATCH 48/94] =?UTF-8?q?chore:=20perceptronCanvas=EC=9D=98=20?= =?UTF-8?q?=ED=9A=8C=EC=A0=84=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A5=BC=20Bas?= =?UTF-8?q?eCanvas=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A1=9C=20=EC=9C=84?= =?UTF-8?q?=EC=9E=84(=EC=B1=85=EC=9E=84=EB=B6=84=EB=A6=AC=20=EB=AA=85?= =?UTF-8?q?=ED=99=95=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 7 ++++--- src/view/components/perceptron/BaseCanvas.ts | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 8691615..b3e3a76 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -9,19 +9,19 @@ import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts'; import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts'; import DataStore from './DataStore.ts'; +import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; import NodeRenderer from '@/view/components/perceptron/NodeRenderer.ts'; import eventBus from './EventBus.ts'; import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts'; import { NETWORK_CONFIG } from './constants/networkConfig.ts'; -import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js'; import { IWeightManager } from '@/core/types/WeightManager.ts'; import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; -import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; +import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { private $WM: IWeightManager; @@ -47,12 +47,13 @@ class AppController { const nodeHandler = new NodeHandler({ networkConfig: NETWORK_CONFIG, nodeRenderer, + perceptronBaseCanvas, }); const $PC = new PerceptronController({ nodeRenderer: nodeRenderer, nodePositionHandler: nodeHandler, edgePositionHandler: new EdgePositionHandler(), - scrollEventHandler: new ScrollEventHandler(), + scrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { console.log('RESULT CHANGED', data); diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index fed87c4..a21f3b6 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -1,6 +1,5 @@ import eventBus from '@/controller/EventBus.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; -import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; import { nodePath } from '@/view/components/perceptron/shapeVector/nodePath.ts'; class BaseCanvas { @@ -12,10 +11,16 @@ class BaseCanvas { this.canvas = document.getElementById('perceptron') as HTMLCanvasElement | null; this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); - this.setupCanvas(); // canvas element setu - this.setupRenderTransform(this.ctx, this.nodes, this.degree, this.rotationDelta); // apply state for rendering context(transform/rotate..) + this.setupCanvas(); // canvas element setup + this.setupRenderTransform( + this.ctx, + this.nodes, + RENDERER_CONFIG.degree, + RENDERER_CONFIG.rotationDelta, + ); // apply state for rendering context(transform/rotate..) } + getCanvas = (): HTMLCanvasElement => this.canvas; getCtx = (): CanvasRenderingContext2D => this.ctx; setupCanvas() { @@ -34,10 +39,15 @@ class BaseCanvas { rotationDelta: number, ): void { ctx.translate(this.canvasCenter.x, this.canvasCenter.y); - ctx.rotate(degree * 180); - ctx.rotate((degree * 180 - degree * rotationDelta * nodes) / 2); + this.rotateCanvas(true, degree * 180); + this.rotateCanvas(true, (degree * 180 - degree * rotationDelta * nodes) / 2); } + rotateCanvas = (clockWise: boolean, value: number) => { + const rotationVector = clockWise ? 1 : -1; + this.ctx.rotate(rotationVector * value); + }; + clearCanvas() { this.ctx.translate(-this.canvasCenter.x, -this.canvasCenter.y); this.ctx.fillStyle = 'rgb(255,255,255)'; From e847196c9a31d0a4d1ee598511312bd17c71ab14 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Thu, 9 Oct 2025 20:46:02 +0900 Subject: [PATCH 49/94] =?UTF-8?q?WIP:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 50 ++++++++++++++++--- .../perceptron/ScrollEventHandler.ts | 3 +- src/view/components/perceptron/BaseCanvas.ts | 39 +++++++++------ src/view/styles/main.css | 3 +- 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 738f727..33678d1 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -61,21 +61,28 @@ export class NodeHandler { // }); const scroll = mouseScroll; const touch = touchScroll; + // console.log(scroll, touch); + + this.BaseCanvas.clearCanvas(); Object.keys(this.nodeObjectSet).forEach((key: string) => { + // 각 레이어의 수를 기준으로 중앙정렬 회전 + console.log('레이어당 노드수', this.nodeObjectSet[key].length); + console.log('기준 노드', this.nodeObjectSet[key].length / 2); + this.BaseCanvas.grid(300); + this.BaseCanvas.saveState(); // 현재의 상태를 저장(레이어 기준) this.BaseCanvas.rotateCanvas( true, (RENDERER_CONFIG.degree * 180 - RENDERER_CONFIG.degree * RENDERER_CONFIG.rotationDelta * - Object.keys(this.nodeObjectSet).length) / + this.nodeObjectSet[key].length) / 2, ); + const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 - Array.from({ length: layerSize }, (_: unknown, i: number) => { - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - return i; - }) + this.BaseCanvas.saveState(); // 현재 상태를 저장(노드 기준) + Array.from({ length: layerSize }, (_: unknown, i: number) => i) .filter( (i: number) => i >= @@ -88,12 +95,43 @@ export class NodeHandler { (mouseScroll + touchScroll) / scrollDivider, ) .forEach((i) => { - console.log('돌아가고 있니..?'); this.nodeRenderer.drawNode(this.nodeObjectSet[key][i].getValue()); + this.drawText(i); this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + this.BaseCanvas.grid(200); }); + // this.BaseCanvas.rotateCanvas( + // false, + // degree * (rotationDelta * this.nodeObjectSet[key].length), + // ); + this.BaseCanvas.restoreState(); + + // this.BaseCanvas.rotateCanvas( + // false, + // (RENDERER_CONFIG.degree * 180 - + // RENDERER_CONFIG.degree * + // RENDERER_CONFIG.rotationDelta * + // this.nodeObjectSet[key].length) / + // 2, + // ); + + this.BaseCanvas.restoreState(); + this.BaseCanvas.moveCanvas(0, 20); + this.drawText(`${key}`); }); + this.BaseCanvas.moveCanvas(0, -60); + this.drawText('렌더 완전 끝남'); + this.BaseCanvas.grid(50); + // TODO: 각 레이별로 중심점 을 옮겨서 계층을 분리할 수 있게 작업 + // TODO: 각 레이어별 돌아간 회전 Stack만큼 Canvas 역회전 + // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } + + drawText(text: number): void { + this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; + this.BaseCanvas.getCtx().fillText(`${text}`, 180, 0); + this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; + } } diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index ab6b791..086d60f 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -57,6 +57,7 @@ class ScrollEventHandler { this.touchDirection = moveX - touchStartX; if (this.touchDirection < 0) { + // console.log('캔버스 돌아감'); this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / scrollDivider)); this.touchMove += 1; this.rotationStack += 1; @@ -65,7 +66,7 @@ class ScrollEventHandler { this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / scrollDivider)); this.touchMove -= 1; this.rotationStack -= 1; - eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove0); + eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); } }); } diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index a21f3b6..43a2fae 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -12,12 +12,7 @@ class BaseCanvas { this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); this.setupCanvas(); // canvas element setup - this.setupRenderTransform( - this.ctx, - this.nodes, - RENDERER_CONFIG.degree, - RENDERER_CONFIG.rotationDelta, - ); // apply state for rendering context(transform/rotate..) + this.setupRenderTransform(this.ctx, RENDERER_CONFIG.degree); // apply state for rendering context(transform/rotate..) } getCanvas = (): HTMLCanvasElement => this.canvas; @@ -25,28 +20,26 @@ class BaseCanvas { setupCanvas() { this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight * (5 / 20); + // this.canvas.height = window.innerHeight * (5 / 20); + this.canvas.height = window.innerHeight; this.ctx.fillStyle = 'rgb(186,186,186)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.canvasCenter = { x: this.canvas.width / 2, y: this.canvas.height / 2 }; } - setupRenderTransform( - ctx: CanvasRenderingContext2D, - nodes: number, - degree: number, - rotationDelta: number, - ): void { + setupRenderTransform(ctx: CanvasRenderingContext2D, degree: number): void { ctx.translate(this.canvasCenter.x, this.canvasCenter.y); this.rotateCanvas(true, degree * 180); - this.rotateCanvas(true, (degree * 180 - degree * rotationDelta * nodes) / 2); } 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.ctx.translate(-this.canvasCenter.x, -this.canvasCenter.y); @@ -54,6 +47,24 @@ class BaseCanvas { this.ctx.fillRect(0, 0, this.canvasCenter.x * 2, this.canvasCenter.y * 2); this.ctx.translate(this.canvasCenter.x, this.canvasCenter.y); } + + saveState() { + console.log('canvas saved'); + this.ctx.save(); + } + + restoreState() { + console.log('restoring state'); + this.ctx.restore(); + } + + grid(distance: number): void { + this.ctx.beginPath(); + this.ctx.moveTo(0, 0); + this.ctx.lineTo(distance, 0); + this.ctx.stroke(); + this.ctx.moveTo(0, 0); + } } export default BaseCanvas; diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 4bf025c..1aaa9bd 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -34,7 +34,8 @@ } #perceptron-section { - position: relative; + /*position: relative;*/ + position: absolute; bottom: -45px; left: 0; From 352170bd794fc19dc6a86789bbbe309b7840d86e Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 11 Oct 2025 20:23:07 +0900 Subject: [PATCH 50/94] =?UTF-8?q?WIP:=20=ED=8D=BC=EC=85=89=ED=8A=B8?= =?UTF-8?q?=EB=A1=A0=20=EC=8B=9C=EA=B0=81=ED=99=94=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 2 +- src/controller/perceptron/EdgeHandler.ts | 4 +- src/controller/perceptron/NodeHandler.ts | 71 ++++++++----------- src/view/components/perceptron/BaseCanvas.ts | 4 +- .../components/perceptron/EdgeRenderer.ts | 3 + src/view/components/perceptron/Node.ts | 10 +-- .../components/perceptron/NodeRenderer.ts | 4 +- .../perceptron/shapeVector/nodePath.ts | 7 +- src/view/styles/main.css | 13 ++-- 9 files changed, 55 insertions(+), 63 deletions(-) create mode 100644 src/view/components/perceptron/EdgeRenderer.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index b3e3a76..0bc1b7c 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -20,7 +20,7 @@ import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; -import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; +import EdgePositionHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 8ab1b00..fa5858a 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1 +1,3 @@ -export class EdgePositionHandler {} +class EdgePositionHandler {} + +export default EdgePositionHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 33678d1..b2969b2 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -44,7 +44,7 @@ export class NodeHandler { const normalizedNetworkConfig = { inputNodes: inputNodes / 8, ...rest }; // 원활한 시각화를 위해 입력 레이어의 크기를 compress Object.keys(this.nodeObjectSet).forEach((key: string) => { this.nodeObjectSet[key] = Array.from({ length: normalizedNetworkConfig[key] }, () => { - return new Node(0); + return new Node(15); }); }); this.render(0, 0); @@ -65,7 +65,7 @@ export class NodeHandler { this.BaseCanvas.clearCanvas(); - Object.keys(this.nodeObjectSet).forEach((key: string) => { + Object.keys(this.nodeObjectSet).forEach((key: string, index: number) => { // 각 레이어의 수를 기준으로 중앙정렬 회전 console.log('레이어당 노드수', this.nodeObjectSet[key].length); console.log('기준 노드', this.nodeObjectSet[key].length / 2); @@ -73,58 +73,45 @@ export class NodeHandler { this.BaseCanvas.saveState(); // 현재의 상태를 저장(레이어 기준) this.BaseCanvas.rotateCanvas( true, - (RENDERER_CONFIG.degree * 180 - - RENDERER_CONFIG.degree * - RENDERER_CONFIG.rotationDelta * - this.nodeObjectSet[key].length) / - 2, - ); + degree * 90 - (degree * rotationDelta * this.nodeObjectSet[key].length) / 2, + ); // 캔버스를 기준으로 레이어를 가운데 정렬 const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 - this.BaseCanvas.saveState(); // 현재 상태를 저장(노드 기준) - Array.from({ length: layerSize }, (_: unknown, i: number) => i) - .filter( - (i: number) => - i >= - layerSize / 2 - - displayNodes / 2 + - (mouseScroll + touchScroll) / scrollDivider && - i < - layerSize / 2 + - displayNodes / 2 + - (mouseScroll + touchScroll) / scrollDivider, - ) - .forEach((i) => { - this.nodeRenderer.drawNode(this.nodeObjectSet[key][i].getValue()); + + const color = ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)']; + const size = [350, 380, 410]; + this.BaseCanvas.getCtx().strokeStyle = color[index]; + Array.from({ length: layerSize }, (_: unknown, i: number) => i).forEach((i) => { + if ( + i >= + layerSize / 2 - + displayNodes / 2 + + (mouseScroll + touchScroll) / scrollDivider && + i < + layerSize / 2 + + displayNodes / 2 + + (mouseScroll + touchScroll) / scrollDivider + ) { + this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); this.drawText(i); this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - this.BaseCanvas.grid(200); - }); - // this.BaseCanvas.rotateCanvas( - // false, - // degree * (rotationDelta * this.nodeObjectSet[key].length), - // ); - this.BaseCanvas.restoreState(); - - // this.BaseCanvas.rotateCanvas( - // false, - // (RENDERER_CONFIG.degree * 180 - - // RENDERER_CONFIG.degree * - // RENDERER_CONFIG.rotationDelta * - // this.nodeObjectSet[key].length) / - // 2, - // ); - + // this.BaseCanvas.grid(size[index]); + } else { + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + } + }); this.BaseCanvas.restoreState(); - this.BaseCanvas.moveCanvas(0, 20); this.drawText(`${key}`); }); - this.BaseCanvas.moveCanvas(0, -60); this.drawText('렌더 완전 끝남'); this.BaseCanvas.grid(50); // TODO: 각 레이별로 중심점 을 옮겨서 계층을 분리할 수 있게 작업 // TODO: 각 레이어별 돌아간 회전 Stack만큼 Canvas 역회전 + // TODO: 노드에 관한 간선 연결 시각화 로직 구현 + // TODO: 코드 리팩토링 + // TODO: 반응형 설계 및 레이아웃 구현 + // TODO: perceptron Canvas 위치 잡기 // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index 43a2fae..9195871 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -19,9 +19,9 @@ class BaseCanvas { getCtx = (): CanvasRenderingContext2D => this.ctx; setupCanvas() { - this.canvas.width = window.innerWidth; + this.canvas.width = 1000; // this.canvas.height = window.innerHeight * (5 / 20); - this.canvas.height = window.innerHeight; + this.canvas.height = 1000; this.ctx.fillStyle = 'rgb(186,186,186)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); diff --git a/src/view/components/perceptron/EdgeRenderer.ts b/src/view/components/perceptron/EdgeRenderer.ts new file mode 100644 index 0000000..652b12b --- /dev/null +++ b/src/view/components/perceptron/EdgeRenderer.ts @@ -0,0 +1,3 @@ +class EdgeRenderer {} + +export default EdgeRenderer; diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/Node.ts index 46a0c7f..1af0223 100644 --- a/src/view/components/perceptron/Node.ts +++ b/src/view/components/perceptron/Node.ts @@ -1,14 +1,14 @@ import { value } from '@/view/components/perceptron/types/Node.ts'; export class Node { - private value: value; - constructor(value: value) { + private value: Number; + constructor(value: number) { this.value = value; } - setValue(value: value): void { + setValue(value: number): void { this.value = value; } - getValue(value: value): number { - this.value = value; + getValue(value: number): number { + return this.value; } } diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index 8190a83..59f6d1e 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -26,9 +26,9 @@ class nodeRenderer { this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler } - drawNode = (p: number): void => { + drawNode = (layer: number, p: number): void => { console.log('그려짐.'); - nodePath(this.renderCtx, { x: 200, y: 0, width: 10, height: 10, percent: p }); + nodePath(this.renderCtx, { x: layer, y: 0, width: 15, height: 15, percent: p }); }; } diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts index 511a502..5ba899f 100644 --- a/src/view/components/perceptron/shapeVector/nodePath.ts +++ b/src/view/components/perceptron/shapeVector/nodePath.ts @@ -2,21 +2,20 @@ import { INodePath } from '@/view/components/perceptron/shape/types/nodePath.ts' export const nodePath = ( ctx: CanvasRenderingContext2D, - { x, y, width, height, radius = 8, percent }: INodePath, + { x, y, width, height, radius = 6, percent }: INodePath, ) => { const node: Path2D = roundedRect(x, y, width, height, radius); ctx.fillStyle = '#fff'; - ctx.strokeStyle = '#000'; + // ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.fill(node); ctx.stroke(node); if (percent > 0) { const filledHeight = (height * percent) / 100; - const fillY = y + height - filledHeight; - const fillPath = roundedRect(x, fillY, width, filledHeight, radius); + const fillPath = roundedRect(x, y, filledHeight, height, radius); ctx.save(); ctx.clip(node); ctx.fillStyle = '#000'; diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 1aaa9bd..efae606 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -23,7 +23,8 @@ #user-input-canvas { position: relative; z-index: 1; - clip-path: ellipse(140% 100% at 50% 100%); + /*clip-path: ellipse(140% 100% at 50% 100%);*/ + clip-path: ellipse(100% 100% at 50% 100%); } #user-input-section > #clear { position: absolute; @@ -39,9 +40,9 @@ bottom: -45px; left: 0; - width: 100%; - #perceptron { - width: 100%; - height: 100%; - } + /*width: 100%;*/ + /*#perceptron {*/ + /* width: 100%;*/ + /* height: 100%;*/ + /*}*/ } From 8c9ffa651cbb38d4dfd0da6d8e0369a580cdfbaa Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Thu, 16 Oct 2025 17:12:50 +0900 Subject: [PATCH 51/94] =?UTF-8?q?WIP:=ED=82=A4=ED=85=8D=EC=B2=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 7 +- src/controller/perceptron/EdgeHandler.ts | 2 +- src/controller/perceptron/NodeHandler.ts | 16 +--- .../perceptron/PerceptronController.ts | 84 ++++++++++++++++--- .../perceptron/types/PerceptronController.ts | 9 +- .../perceptron/utils/normalizeNetworkInfo.ts | 5 ++ 6 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 src/controller/perceptron/utils/normalizeNetworkInfo.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 0bc1b7c..81e7270 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -20,7 +20,7 @@ import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; -import EdgePositionHandler from '@/controller/perceptron/EdgeHandler.ts'; +import EdgeHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { @@ -51,8 +51,9 @@ class AppController { }); const $PC = new PerceptronController({ nodeRenderer: nodeRenderer, - nodePositionHandler: nodeHandler, - edgePositionHandler: new EdgePositionHandler(), + nodeHandler: nodeHandler, + edgeHandler: new EdgeHandler(), + baseCanvas: perceptronBaseCanvas scrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index fa5858a..3e065c8 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,3 @@ -class EdgePositionHandler {} +class EdgeHandler {} export default EdgePositionHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index b2969b2..fcdeddf 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -1,7 +1,6 @@ import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; import { Node } from '@/view/components/perceptron/Node.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; -const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import eventBus from '@/controller/eventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; @@ -33,28 +32,20 @@ export class NodeHandler { this.nodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; - this.initialize(networkConfig); this.registerScrollEvent(); console.log(this.nodeObjectSet); } - initialize(networkConfig: networkConfig): void { - const { inputNodes, ...rest } = networkConfig; - const normalizedNetworkConfig = { inputNodes: inputNodes / 8, ...rest }; // 원활한 시각화를 위해 입력 레이어의 크기를 compress + initializeNode(networkInfo: any): void { Object.keys(this.nodeObjectSet).forEach((key: string) => { - this.nodeObjectSet[key] = Array.from({ length: normalizedNetworkConfig[key] }, () => { - return new Node(15); + this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, () => { + return new Node(0); }); }); this.render(0, 0); } - registerScrollEvent() { - eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => this.render(mouseScroll, 0)); - eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => this.render(0, touchScroll)); - } - render(mouseScroll: number, touchScroll: number): void { // Object.keys(this.nodeObjectSet).forEach((key: string) => { // console.log('key:', key, 'value:', this.nodeObjectSet[key]); @@ -112,6 +103,7 @@ export class NodeHandler { // TODO: 코드 리팩토링 // TODO: 반응형 설계 및 레이아웃 구현 // TODO: perceptron Canvas 위치 잡기 + // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index cf1817c..18954c6 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -2,31 +2,93 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/Percep import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; -import { DATA_EVENTS, DataEvent } from '@/controller/constants/events.ts'; +import { DATA_EVENTS, DataEvent, SCROLL_EVENTS } from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; +import { networkConfig } from '@/controller/constants/networkConfig.ts'; +import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; + +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; class PerceptronController { - private nodePositionHandler: NodeHandler; - private edgePositionHandler: EdgePositionHandler; - private scrollEventHandler: ScrollEventHandler; + private NodeHandler: INodeHandler; + private EdgeHandler: IEdgePositionHandler; + private BaseCanvas: IBaseCanvas; + private ScrollEventHandler: IScrollEventHandler; + + private normalizedNetworkInfo: any; constructor({ - nodeRenderer, - nodePositionHandler, - edgePositionHandler, - scrollEventHandler, + NodeRenderer, + NodeHandler, + EdgeHandler, + BaseCanvas, + ScrollEventHandler, }: IPerceptronControllerProps) { - this.nodePositionHandler = nodePositionHandler; - this.edgePositionHandler = edgePositionHandler; - this.scrollEventHandler = scrollEventHandler; + this.NodeHandler = NodeHandler; + this.EdgeHandler = EdgeHandler; + this.BaseCanvas = BaseCanvas; + this.ScrollEventHandler = ScrollEventHandler; + + this.initializeNodeValue(); + eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); }); } + + initializeNodeValue() { + this.normalizedNetworkInfo = normalizeNetworkConfig(networkConfig); // 원활한 시각화를 위해 입력 레이어의 크기를 compress + this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + } + + registerScrollEvent() { + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => + this.calculateNodePosition(mouseScroll, 0), + ); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => + this.calculateNodePosition(0, touchScroll), + ); + } + + calculateNodePosition(mouseScroll: number, touchScroll: number) { + Object.keys(this.normalizedNetworkInfo).map((key: string, index: number) => { + this.BaseCanvas.rotateCanvas( + true, + degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2, + ); + this.BaseCanvas.saveState(); + const layerSize = this.normalizedNetworkInfo[key]; + + const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; + const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; + const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; + + const displayCondition = i >= displayStart && i < displayEnd; + + Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { + if (displayCondition) { + this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); + this.drawText(i); + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + } else { + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + } + }); + }); + + // Position, Rotation 값 Object로 넘기기 + } + + calculateEdgePosition() { + // Position 값 Object로 넘기기 + } + + renderPerceptron() {} } export default PerceptronController; diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index 300d3d8..e2d5ae5 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -1,6 +1,7 @@ export interface IPerceptronControllerProps { - nodePositionHandler: INodePositionHandler; - edgePositionHandler: INdgePositionHandler; - nodeRenderer: INodeRenderer; - scrollEventHandler: IScrollEventHandler; + NodeHandler: INodePositionHandler; + EdgeHandler: INdgePositionHandler; + NodeRenderer: INodeRenderer; + BaseCanvas: IBasecanvas; + ScrollEventHandler: IScrollEventHandler; } diff --git a/src/controller/perceptron/utils/normalizeNetworkInfo.ts b/src/controller/perceptron/utils/normalizeNetworkInfo.ts new file mode 100644 index 0000000..38109f6 --- /dev/null +++ b/src/controller/perceptron/utils/normalizeNetworkInfo.ts @@ -0,0 +1,5 @@ +export const normalizeNetworkConfig = (networkConfig: any) => { + const { inputNodes, ...rest } = networkConfig; + const normalizedNetworkInfo = { inputNodes: inputNodes / 8, ...rest }; + return normalizedNetworkInfo; +}; From 51ec2e5e447e371f949e1e744d1e5994f07ae12e Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Thu, 16 Oct 2025 17:23:20 +0900 Subject: [PATCH 52/94] =?UTF-8?q?WIP:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 8c9ffa651cbb38d4dfd0da6d8e0369a580cdfbaa. --- src/controller/AppController.ts | 7 +- src/controller/perceptron/EdgeHandler.ts | 2 +- src/controller/perceptron/NodeHandler.ts | 16 +++- .../perceptron/PerceptronController.ts | 84 +++---------------- .../perceptron/types/PerceptronController.ts | 9 +- .../perceptron/utils/normalizeNetworkInfo.ts | 5 -- 6 files changed, 31 insertions(+), 92 deletions(-) delete mode 100644 src/controller/perceptron/utils/normalizeNetworkInfo.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 81e7270..0bc1b7c 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -20,7 +20,7 @@ import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; -import EdgeHandler from '@/controller/perceptron/EdgeHandler.ts'; +import EdgePositionHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { @@ -51,9 +51,8 @@ class AppController { }); const $PC = new PerceptronController({ nodeRenderer: nodeRenderer, - nodeHandler: nodeHandler, - edgeHandler: new EdgeHandler(), - baseCanvas: perceptronBaseCanvas + nodePositionHandler: nodeHandler, + edgePositionHandler: new EdgePositionHandler(), scrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 3e065c8..fa5858a 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,3 @@ -class EdgeHandler {} +class EdgePositionHandler {} export default EdgePositionHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index fcdeddf..b2969b2 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -1,6 +1,7 @@ import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; import { Node } from '@/view/components/perceptron/Node.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import eventBus from '@/controller/eventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; @@ -32,20 +33,28 @@ export class NodeHandler { this.nodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; + this.initialize(networkConfig); this.registerScrollEvent(); console.log(this.nodeObjectSet); } - initializeNode(networkInfo: any): void { + initialize(networkConfig: networkConfig): void { + const { inputNodes, ...rest } = networkConfig; + const normalizedNetworkConfig = { inputNodes: inputNodes / 8, ...rest }; // 원활한 시각화를 위해 입력 레이어의 크기를 compress Object.keys(this.nodeObjectSet).forEach((key: string) => { - this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, () => { - return new Node(0); + this.nodeObjectSet[key] = Array.from({ length: normalizedNetworkConfig[key] }, () => { + return new Node(15); }); }); this.render(0, 0); } + registerScrollEvent() { + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => this.render(mouseScroll, 0)); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => this.render(0, touchScroll)); + } + render(mouseScroll: number, touchScroll: number): void { // Object.keys(this.nodeObjectSet).forEach((key: string) => { // console.log('key:', key, 'value:', this.nodeObjectSet[key]); @@ -103,7 +112,6 @@ export class NodeHandler { // TODO: 코드 리팩토링 // TODO: 반응형 설계 및 레이아웃 구현 // TODO: perceptron Canvas 위치 잡기 - // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 18954c6..cf1817c 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -2,93 +2,31 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/Percep import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; -import { DATA_EVENTS, DataEvent, SCROLL_EVENTS } from '@/controller/constants/events.ts'; +import { DATA_EVENTS, DataEvent } from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; -import { networkConfig } from '@/controller/constants/networkConfig.ts'; -import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; - -import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; -const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; class PerceptronController { - private NodeHandler: INodeHandler; - private EdgeHandler: IEdgePositionHandler; - private BaseCanvas: IBaseCanvas; - private ScrollEventHandler: IScrollEventHandler; - - private normalizedNetworkInfo: any; + private nodePositionHandler: NodeHandler; + private edgePositionHandler: EdgePositionHandler; + private scrollEventHandler: ScrollEventHandler; constructor({ - NodeRenderer, - NodeHandler, - EdgeHandler, - BaseCanvas, - ScrollEventHandler, + nodeRenderer, + nodePositionHandler, + edgePositionHandler, + scrollEventHandler, }: IPerceptronControllerProps) { - this.NodeHandler = NodeHandler; - this.EdgeHandler = EdgeHandler; - this.BaseCanvas = BaseCanvas; - this.ScrollEventHandler = ScrollEventHandler; - - this.initializeNodeValue(); - + this.nodePositionHandler = nodePositionHandler; + this.edgePositionHandler = edgePositionHandler; + this.scrollEventHandler = scrollEventHandler; eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); }); } - - initializeNodeValue() { - this.normalizedNetworkInfo = normalizeNetworkConfig(networkConfig); // 원활한 시각화를 위해 입력 레이어의 크기를 compress - this.NodeHandler.initializeNode(this.normalizedNetworkInfo); - } - - registerScrollEvent() { - eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => - this.calculateNodePosition(mouseScroll, 0), - ); - eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => - this.calculateNodePosition(0, touchScroll), - ); - } - - calculateNodePosition(mouseScroll: number, touchScroll: number) { - Object.keys(this.normalizedNetworkInfo).map((key: string, index: number) => { - this.BaseCanvas.rotateCanvas( - true, - degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2, - ); - this.BaseCanvas.saveState(); - const layerSize = this.normalizedNetworkInfo[key]; - - const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; - const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; - const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; - - const displayCondition = i >= displayStart && i < displayEnd; - - Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { - if (displayCondition) { - this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); - this.drawText(i); - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - } else { - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - } - }); - }); - - // Position, Rotation 값 Object로 넘기기 - } - - calculateEdgePosition() { - // Position 값 Object로 넘기기 - } - - renderPerceptron() {} } export default PerceptronController; diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index e2d5ae5..300d3d8 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -1,7 +1,6 @@ export interface IPerceptronControllerProps { - NodeHandler: INodePositionHandler; - EdgeHandler: INdgePositionHandler; - NodeRenderer: INodeRenderer; - BaseCanvas: IBasecanvas; - ScrollEventHandler: IScrollEventHandler; + nodePositionHandler: INodePositionHandler; + edgePositionHandler: INdgePositionHandler; + nodeRenderer: INodeRenderer; + scrollEventHandler: IScrollEventHandler; } diff --git a/src/controller/perceptron/utils/normalizeNetworkInfo.ts b/src/controller/perceptron/utils/normalizeNetworkInfo.ts deleted file mode 100644 index 38109f6..0000000 --- a/src/controller/perceptron/utils/normalizeNetworkInfo.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const normalizeNetworkConfig = (networkConfig: any) => { - const { inputNodes, ...rest } = networkConfig; - const normalizedNetworkInfo = { inputNodes: inputNodes / 8, ...rest }; - return normalizedNetworkInfo; -}; From 448de7111cf7579b9a6e255b150f49afd7feffe4 Mon Sep 17 00:00:00 2001 From: Twince Date: Fri, 17 Oct 2025 16:49:00 +0900 Subject: [PATCH 53/94] =?UTF-8?q?Revert=20"WIP:=20=EC=95=84=ED=82=A4?= =?UTF-8?q?=ED=85=8D=EC=B2=98=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 51ec2e5e447e371f949e1e744d1e5994f07ae12e. --- src/controller/AppController.ts | 7 +- src/controller/perceptron/EdgeHandler.ts | 2 +- src/controller/perceptron/NodeHandler.ts | 16 +--- .../perceptron/PerceptronController.ts | 84 ++++++++++++++++--- .../perceptron/types/PerceptronController.ts | 9 +- .../perceptron/utils/normalizeNetworkInfo.ts | 5 ++ 6 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 src/controller/perceptron/utils/normalizeNetworkInfo.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 0bc1b7c..81e7270 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -20,7 +20,7 @@ import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts'; import { IDataStore } from './types/DataStore.ts'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; -import EdgePositionHandler from '@/controller/perceptron/EdgeHandler.ts'; +import EdgeHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; class AppController { @@ -51,8 +51,9 @@ class AppController { }); const $PC = new PerceptronController({ nodeRenderer: nodeRenderer, - nodePositionHandler: nodeHandler, - edgePositionHandler: new EdgePositionHandler(), + nodeHandler: nodeHandler, + edgeHandler: new EdgeHandler(), + baseCanvas: perceptronBaseCanvas scrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index fa5858a..3e065c8 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,3 @@ -class EdgePositionHandler {} +class EdgeHandler {} export default EdgePositionHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index b2969b2..fcdeddf 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -1,7 +1,6 @@ import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; import { Node } from '@/view/components/perceptron/Node.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; -const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import eventBus from '@/controller/eventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; @@ -33,28 +32,20 @@ export class NodeHandler { this.nodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; - this.initialize(networkConfig); this.registerScrollEvent(); console.log(this.nodeObjectSet); } - initialize(networkConfig: networkConfig): void { - const { inputNodes, ...rest } = networkConfig; - const normalizedNetworkConfig = { inputNodes: inputNodes / 8, ...rest }; // 원활한 시각화를 위해 입력 레이어의 크기를 compress + initializeNode(networkInfo: any): void { Object.keys(this.nodeObjectSet).forEach((key: string) => { - this.nodeObjectSet[key] = Array.from({ length: normalizedNetworkConfig[key] }, () => { - return new Node(15); + this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, () => { + return new Node(0); }); }); this.render(0, 0); } - registerScrollEvent() { - eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => this.render(mouseScroll, 0)); - eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => this.render(0, touchScroll)); - } - render(mouseScroll: number, touchScroll: number): void { // Object.keys(this.nodeObjectSet).forEach((key: string) => { // console.log('key:', key, 'value:', this.nodeObjectSet[key]); @@ -112,6 +103,7 @@ export class NodeHandler { // TODO: 코드 리팩토링 // TODO: 반응형 설계 및 레이아웃 구현 // TODO: perceptron Canvas 위치 잡기 + // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index cf1817c..18954c6 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -2,31 +2,93 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/Percep import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; -import { DATA_EVENTS, DataEvent } from '@/controller/constants/events.ts'; +import { DATA_EVENTS, DataEvent, SCROLL_EVENTS } from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; +import { networkConfig } from '@/controller/constants/networkConfig.ts'; +import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; + +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; class PerceptronController { - private nodePositionHandler: NodeHandler; - private edgePositionHandler: EdgePositionHandler; - private scrollEventHandler: ScrollEventHandler; + private NodeHandler: INodeHandler; + private EdgeHandler: IEdgePositionHandler; + private BaseCanvas: IBaseCanvas; + private ScrollEventHandler: IScrollEventHandler; + + private normalizedNetworkInfo: any; constructor({ - nodeRenderer, - nodePositionHandler, - edgePositionHandler, - scrollEventHandler, + NodeRenderer, + NodeHandler, + EdgeHandler, + BaseCanvas, + ScrollEventHandler, }: IPerceptronControllerProps) { - this.nodePositionHandler = nodePositionHandler; - this.edgePositionHandler = edgePositionHandler; - this.scrollEventHandler = scrollEventHandler; + this.NodeHandler = NodeHandler; + this.EdgeHandler = EdgeHandler; + this.BaseCanvas = BaseCanvas; + this.ScrollEventHandler = ScrollEventHandler; + + this.initializeNodeValue(); + eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); }); } + + initializeNodeValue() { + this.normalizedNetworkInfo = normalizeNetworkConfig(networkConfig); // 원활한 시각화를 위해 입력 레이어의 크기를 compress + this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + } + + registerScrollEvent() { + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => + this.calculateNodePosition(mouseScroll, 0), + ); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => + this.calculateNodePosition(0, touchScroll), + ); + } + + calculateNodePosition(mouseScroll: number, touchScroll: number) { + Object.keys(this.normalizedNetworkInfo).map((key: string, index: number) => { + this.BaseCanvas.rotateCanvas( + true, + degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2, + ); + this.BaseCanvas.saveState(); + const layerSize = this.normalizedNetworkInfo[key]; + + const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; + const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; + const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; + + const displayCondition = i >= displayStart && i < displayEnd; + + Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { + if (displayCondition) { + this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); + this.drawText(i); + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + } else { + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + } + }); + }); + + // Position, Rotation 값 Object로 넘기기 + } + + calculateEdgePosition() { + // Position 값 Object로 넘기기 + } + + renderPerceptron() {} } export default PerceptronController; diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index 300d3d8..e2d5ae5 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -1,6 +1,7 @@ export interface IPerceptronControllerProps { - nodePositionHandler: INodePositionHandler; - edgePositionHandler: INdgePositionHandler; - nodeRenderer: INodeRenderer; - scrollEventHandler: IScrollEventHandler; + NodeHandler: INodePositionHandler; + EdgeHandler: INdgePositionHandler; + NodeRenderer: INodeRenderer; + BaseCanvas: IBasecanvas; + ScrollEventHandler: IScrollEventHandler; } diff --git a/src/controller/perceptron/utils/normalizeNetworkInfo.ts b/src/controller/perceptron/utils/normalizeNetworkInfo.ts new file mode 100644 index 0000000..38109f6 --- /dev/null +++ b/src/controller/perceptron/utils/normalizeNetworkInfo.ts @@ -0,0 +1,5 @@ +export const normalizeNetworkConfig = (networkConfig: any) => { + const { inputNodes, ...rest } = networkConfig; + const normalizedNetworkInfo = { inputNodes: inputNodes / 8, ...rest }; + return normalizedNetworkInfo; +}; From 69316d458c53da6001392b4b3b15f6b5d39e67c2 Mon Sep 17 00:00:00 2001 From: Twince Date: Sat, 18 Oct 2025 20:26:22 +0900 Subject: [PATCH 54/94] =?UTF-8?q?WIP:=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EC=88=98=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=8D=BC=EC=85=89=ED=8A=B8=EB=A1=A0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 10 ++++---- src/controller/perceptron/EdgeHandler.ts | 2 +- src/controller/perceptron/NodeHandler.ts | 13 ++++------ .../perceptron/PerceptronController.ts | 25 +++++++++++-------- .../perceptron/types/nodeObjectSet.ts | 7 ++++++ src/view/components/perceptron/Node.ts | 2 +- .../components/perceptron/NodeRenderer.ts | 1 - 7 files changed, 34 insertions(+), 26 deletions(-) create mode 100644 src/controller/perceptron/types/nodeObjectSet.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 81e7270..0ea475c 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -50,11 +50,11 @@ class AppController { perceptronBaseCanvas, }); const $PC = new PerceptronController({ - nodeRenderer: nodeRenderer, - nodeHandler: nodeHandler, - edgeHandler: new EdgeHandler(), - baseCanvas: perceptronBaseCanvas - scrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), + NodeRenderer: nodeRenderer, + NodeHandler: nodeHandler, + EdgeHandler: new EdgeHandler(), + BaseCanvas: perceptronBaseCanvas, + ScrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { console.log('RESULT CHANGED', data); diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 3e065c8..7a3d1c3 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,3 @@ class EdgeHandler {} -export default EdgePositionHandler; +export default EdgeHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index fcdeddf..585a50a 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -4,13 +4,10 @@ import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; import eventBus from '@/controller/eventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; +import { nodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; export class NodeHandler { - private readonly nodeObjectSet: { - inputNodes: Array; - hiddenNodes: Array; - outputNodes: Array; - }; + private readonly nodeObjectSet: nodeObjectSet private displayNodes: number; private nodeRenderer: INodeRenderer; private BaseCanvas: IBaseCanvas; @@ -32,18 +29,18 @@ export class NodeHandler { this.nodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; - this.registerScrollEvent(); + // this.registerScrollEvent(); console.log(this.nodeObjectSet); } - initializeNode(networkInfo: any): void { + initializeNode(networkInfo: any): any { Object.keys(this.nodeObjectSet).forEach((key: string) => { this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, () => { return new Node(0); }); }); - this.render(0, 0); + return this.nodeObjectSet; } render(mouseScroll: number, touchScroll: number): void { diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 18954c6..3f84ff7 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -7,20 +7,23 @@ import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; -import { networkConfig } from '@/controller/constants/networkConfig.ts'; +import {NETWORK_CONFIG} from "@/controller/constants/networkConfig"; import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; +import { nodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; class PerceptronController { + private NodeRenderer: INodeRenderer; private NodeHandler: INodeHandler; private EdgeHandler: IEdgePositionHandler; private BaseCanvas: IBaseCanvas; private ScrollEventHandler: IScrollEventHandler; private normalizedNetworkInfo: any; + private nodeObjectSet: nodeObjectSet; constructor({ NodeRenderer, @@ -29,6 +32,7 @@ class PerceptronController { BaseCanvas, ScrollEventHandler, }: IPerceptronControllerProps) { + this.NodeRenderer = NodeRenderer this.NodeHandler = NodeHandler; this.EdgeHandler = EdgeHandler; this.BaseCanvas = BaseCanvas; @@ -42,8 +46,10 @@ class PerceptronController { } initializeNodeValue() { - this.normalizedNetworkInfo = normalizeNetworkConfig(networkConfig); // 원활한 시각화를 위해 입력 레이어의 크기를 compress - this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + this.normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); // 원활한 시각화를 위해 입력 레이어의 크기를 compress + this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + this.NodeHandler.render(0, 0); + } registerScrollEvent() { @@ -64,16 +70,15 @@ class PerceptronController { this.BaseCanvas.saveState(); const layerSize = this.normalizedNetworkInfo[key]; - const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; - const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; - const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; + Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { + const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; + const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; + const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; - const displayCondition = i >= displayStart && i < displayEnd; + const displayCondition = i >= displayStart && i < displayEnd; - Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { if (displayCondition) { - this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); - this.drawText(i); + this.NodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); } else { this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); diff --git a/src/controller/perceptron/types/nodeObjectSet.ts b/src/controller/perceptron/types/nodeObjectSet.ts new file mode 100644 index 0000000..b39758f --- /dev/null +++ b/src/controller/perceptron/types/nodeObjectSet.ts @@ -0,0 +1,7 @@ +import {Node} from "@/view/components/perceptron/Node"; + +export interface NodeObjectSet { + inputNodes: Array; + hiddenNodes: Array; + outputNodes: Array; +} diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/Node.ts index 1af0223..254101c 100644 --- a/src/view/components/perceptron/Node.ts +++ b/src/view/components/perceptron/Node.ts @@ -8,7 +8,7 @@ export class Node { setValue(value: number): void { this.value = value; } - getValue(value: number): number { + getValue(): number { return this.value; } } diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index 59f6d1e..a02e82d 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -27,7 +27,6 @@ class nodeRenderer { } drawNode = (layer: number, p: number): void => { - console.log('그려짐.'); nodePath(this.renderCtx, { x: layer, y: 0, width: 15, height: 15, percent: p }); }; } From e0ab6e4ce6e6e09a6d7ee48241202f5ccd3281e7 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 19 Oct 2025 20:12:01 +0900 Subject: [PATCH 55/94] =?UTF-8?q?WIP:=20=EB=A0=8C=EB=8D=94=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 101 ++++++++++-------- .../perceptron/PerceptronController.ts | 84 +++++++++++---- src/view/components/perceptron/BaseCanvas.ts | 18 ++-- .../components/perceptron/NodeRenderer.ts | 8 +- .../perceptron/shapeVector/nodePath.ts | 2 +- 5 files changed, 136 insertions(+), 77 deletions(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 585a50a..6b43a05 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -7,9 +7,9 @@ import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; import { nodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; export class NodeHandler { - private readonly nodeObjectSet: nodeObjectSet + private readonly nodeObjectSet: nodeObjectSet; private displayNodes: number; - private nodeRenderer: INodeRenderer; + private NodeRenderer: INodeRenderer; private BaseCanvas: IBaseCanvas; constructor({ @@ -26,8 +26,9 @@ export class NodeHandler { hiddenNodes: undefined, outputNodes: undefined, }; - this.nodeRenderer = nodeRenderer; + this.NodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; + console.log(perceptronBaseCanvas); // this.registerScrollEvent(); @@ -43,57 +44,73 @@ export class NodeHandler { return this.nodeObjectSet; } - render(mouseScroll: number, touchScroll: number): void { + render(anchorPosition: any): void { // Object.keys(this.nodeObjectSet).forEach((key: string) => { // console.log('key:', key, 'value:', this.nodeObjectSet[key]); // }); - const scroll = mouseScroll; - const touch = touchScroll; + // const scroll = mouseScroll; + // const touch = touchScroll; // console.log(scroll, touch); this.BaseCanvas.clearCanvas(); + console.log(this.NodeRenderer); + this.drawText('렌더 시작'); - Object.keys(this.nodeObjectSet).forEach((key: string, index: number) => { - // 각 레이어의 수를 기준으로 중앙정렬 회전 - console.log('레이어당 노드수', this.nodeObjectSet[key].length); - console.log('기준 노드', this.nodeObjectSet[key].length / 2); - this.BaseCanvas.grid(300); - this.BaseCanvas.saveState(); // 현재의 상태를 저장(레이어 기준) - this.BaseCanvas.rotateCanvas( - true, - degree * 90 - (degree * rotationDelta * this.nodeObjectSet[key].length) / 2, - ); // 캔버스를 기준으로 레이어를 가운데 정렬 - - const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 - - const color = ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)']; - const size = [350, 380, 410]; - this.BaseCanvas.getCtx().strokeStyle = color[index]; - Array.from({ length: layerSize }, (_: unknown, i: number) => i).forEach((i) => { - if ( - i >= - layerSize / 2 - - displayNodes / 2 + - (mouseScroll + touchScroll) / scrollDivider && - i < - layerSize / 2 + - displayNodes / 2 + - (mouseScroll + touchScroll) / scrollDivider - ) { - this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); - this.drawText(i); - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - // this.BaseCanvas.grid(size[index]); - } else { - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - } + Object.keys(anchorPosition).forEach((key: string) => { + const layerSize = anchorPosition[key].length; + Array.from({ length: layerSize }, (_, i) => { + console.log('anchoer Pos:', anchorPosition['inputLayer'][i]); + this.NodeRenderer.drawNode( + anchorPosition[key][i].posX, + anchorPosition[key][i].posY, + anchorPosition[key][i].percent, + ); }); - this.BaseCanvas.restoreState(); - this.drawText(`${key}`); + console.log('그림을 그려요!'); }); this.drawText('렌더 완전 끝남'); this.BaseCanvas.grid(50); + + // Object.keys(this.nodeObjectSet).forEach((key: string, index: number) => { + // // 각 레이어의 수를 기준으로 중앙정렬 회전 + // console.log('레이어당 노드수', this.nodeObjectSet[key].length); + // console.log('기준 노드', this.nodeObjectSet[key].length / 2); + // this.BaseCanvas.grid(300); + // this.BaseCanvas.saveState(); // 현재의 상태를 저장(레이어 기준) + // this.BaseCanvas.rotateCanvas( + // true, + // degree * 90 - (degree * rotationDelta * this.nodeObjectSet[key].length) / 2, + // ); // 캔버스를 기준으로 레이어를 가운데 정렬 + // + // const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 + // + // const color = ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)']; + // const size = [350, 380, 410]; + // this.BaseCanvas.getCtx().strokeStyle = color[index]; + // Array.from({ length: layerSize }, (_: unknown, i: number) => i).forEach((i) => { + // if ( + // i >= + // layerSize / 2 - + // displayNodes / 2 + + // (mouseScroll + touchScroll) / scrollDivider && + // i < + // layerSize / 2 + + // displayNodes / 2 + + // (mouseScroll + touchScroll) / scrollDivider + // ) { + // this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); + // this.drawText(i); + // this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + // // this.BaseCanvas.grid(size[index]); + // } else { + // this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); + // } + // }); + // this.BaseCanvas.restoreState(); + // this.drawText(`${key}`); + // }); + // TODO: 각 레이별로 중심점 을 옮겨서 계층을 분리할 수 있게 작업 // TODO: 각 레이어별 돌아간 회전 Stack만큼 Canvas 역회전 // TODO: 노드에 관한 간선 연결 시각화 로직 구현 diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 3f84ff7..9142b32 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -7,14 +7,14 @@ import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; -import {NETWORK_CONFIG} from "@/controller/constants/networkConfig"; +import { NETWORK_CONFIG } from '@/controller/constants/networkConfig'; import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; -import { nodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; +import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; class PerceptronController { private NodeRenderer: INodeRenderer; private NodeHandler: INodeHandler; @@ -23,7 +23,10 @@ class PerceptronController { private ScrollEventHandler: IScrollEventHandler; private normalizedNetworkInfo: any; - private nodeObjectSet: nodeObjectSet; + private nodeObjectSet: NodeObjectSet; + private readonly distanceFromCenter: number[]; + private anchorPosition: { inputLayer: number[]; hiddenLayer: number[]; outputLayer: number[] }; + private anchorPosKeys: string[] = ['inputLayer', 'hiddenLayer', 'outputLayer']; constructor({ NodeRenderer, @@ -32,13 +35,18 @@ class PerceptronController { BaseCanvas, ScrollEventHandler, }: IPerceptronControllerProps) { - this.NodeRenderer = NodeRenderer + this.NodeRenderer = NodeRenderer; this.NodeHandler = NodeHandler; this.EdgeHandler = EdgeHandler; this.BaseCanvas = BaseCanvas; this.ScrollEventHandler = ScrollEventHandler; + this.distanceFromCenter = [350, 380, 410]; + this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; + this.initializeNodeValue(); + this.registerScrollEvent(); + this.calculateNodePosition(0, 0); eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); @@ -47,9 +55,8 @@ class PerceptronController { initializeNodeValue() { this.normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); // 원활한 시각화를 위해 입력 레이어의 크기를 compress - this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); this.NodeHandler.render(0, 0); - } registerScrollEvent() { @@ -62,30 +69,61 @@ class PerceptronController { } calculateNodePosition(mouseScroll: number, touchScroll: number) { - Object.keys(this.normalizedNetworkInfo).map((key: string, index: number) => { + this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; + Object.keys(this.normalizedNetworkInfo).map((key: string, layerIndex: number) => { + this.BaseCanvas.saveState(); this.BaseCanvas.rotateCanvas( true, degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2, ); - this.BaseCanvas.saveState(); - const layerSize = this.normalizedNetworkInfo[key]; - Array.from({ length: layerSize }, (_: unknown, i: number) => i).map((i) => { - const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; - const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; - const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; - - const displayCondition = i >= displayStart && i < displayEnd; + const layerSize = this.normalizedNetworkInfo[key]; - if (displayCondition) { - this.NodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - } else { - this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - } - }); + Array.from({ length: layerSize }, (_: unknown, nodeIndex: number) => nodeIndex).map( + (nodeIndex) => { + const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; + const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; + const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; + + const displayCondition = nodeIndex >= displayStart && nodeIndex < displayEnd; + this.BaseCanvas.grid(200); + + this.BaseCanvas.saveState(); + + if (displayCondition) { + // this.NodeRenderer.drawNode( + // this.distanceFromCenter[layerIndex], + // this.nodeObjectSet[key][nodeIndex].getValue(), + // ); + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); + console.log('포지션 저장됨'); + } else { + this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); + } + const currentMatrix = this.BaseCanvas.getCtx().getTransform(); + const globalMatrix = this.BaseCanvas.setupMatrix.multiply(currentMatrix); + + const global = globalMatrix.transformPoint( + new DOMPoint(this.distanceFromCenter[layerIndex], 0), + ); + const angle = Math.atan2(globalMatrix.b, globalMatrix.a); + const rotation = angle * (180 / Math.PI); + + const percent = this.nodeObjectSet[key][nodeIndex].getValue(); + + this.anchorPosition[this.anchorPosKeys[layerIndex]].push({ + posX: global.x, + posY: global.y, + rotation: rotation, + percent: percent, + }); + this.BaseCanvas.grid(50); + this.BaseCanvas.restoreState(); + }, + ); + this.BaseCanvas.restoreState(); }); - + this.NodeHandler.render(this.anchorPosition); // Position, Rotation 값 Object로 넘기기 } diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index 9195871..eb9bb74 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -7,12 +7,15 @@ class BaseCanvas { 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.ctx, RENDERER_CONFIG.degree); // apply state for rendering context(transform/rotate..) + this.setupRenderTransform(); + // this.setupRenderTransform(this.ctx, RENDERER_CONFIG.degree); // apply state for rendering context(transform/rotate..) } getCanvas = (): HTMLCanvasElement => this.canvas; @@ -28,9 +31,12 @@ class BaseCanvas { this.canvasCenter = { x: this.canvas.width / 2, y: this.canvas.height / 2 }; } - setupRenderTransform(ctx: CanvasRenderingContext2D, degree: number): void { - ctx.translate(this.canvasCenter.x, this.canvasCenter.y); - this.rotateCanvas(true, degree * 180); + 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) => { @@ -43,18 +49,16 @@ class BaseCanvas { clearCanvas() { this.ctx.translate(-this.canvasCenter.x, -this.canvasCenter.y); - this.ctx.fillStyle = 'rgb(255,255,255)'; + this.ctx.fillStyle = 'rgb(255,137,137)'; this.ctx.fillRect(0, 0, this.canvasCenter.x * 2, this.canvasCenter.y * 2); this.ctx.translate(this.canvasCenter.x, this.canvasCenter.y); } saveState() { - console.log('canvas saved'); this.ctx.save(); } restoreState() { - console.log('restoring state'); this.ctx.restore(); } diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index a02e82d..83e1d85 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -1,7 +1,7 @@ import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; import { nodePath } from '@/view/components/perceptron/shapeVector/nodePath.ts'; -class nodeRenderer { +class NodeRenderer { private canvasCenter!: { x: number; y: number }; private rotationDelta!: number; private degree!: number; @@ -26,9 +26,9 @@ class nodeRenderer { this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler } - drawNode = (layer: number, p: number): void => { - nodePath(this.renderCtx, { x: layer, y: 0, width: 15, height: 15, percent: p }); + drawNode = (x: number, y: number, p: number): void => { + nodePath(this.renderCtx, { x: x, y: y, width: 15, height: 15, percent: p }); }; } -export default nodeRenderer; +export default NodeRenderer; diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts index 5ba899f..0453eeb 100644 --- a/src/view/components/perceptron/shapeVector/nodePath.ts +++ b/src/view/components/perceptron/shapeVector/nodePath.ts @@ -7,7 +7,7 @@ export const nodePath = ( const node: Path2D = roundedRect(x, y, width, height, radius); ctx.fillStyle = '#fff'; - // ctx.strokeStyle = '#000'; + ctx.strokeStyle = '#000'; ctx.lineWidth = 1; ctx.fill(node); ctx.stroke(node); From 2e06955b70ea1e30950ab92951bc472b58f774b1 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 25 Oct 2025 19:33:20 +0900 Subject: [PATCH 56/94] =?UTF-8?q?feat:=20=EB=85=B8=EB=93=9C=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 22 +++++----- .../perceptron/PerceptronController.ts | 43 ++++++++++--------- .../perceptron/utils/findWidestLayer.ts | 7 +++ .../components/perceptron/NodeRenderer.ts | 11 ++++- .../perceptron/shapeVector/nodePath.ts | 12 ++++-- 5 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 src/controller/perceptron/utils/findWidestLayer.ts diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 6b43a05..cfe4017 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -32,13 +32,14 @@ export class NodeHandler { // this.registerScrollEvent(); - console.log(this.nodeObjectSet); + // console.log(this.nodeObjectSet); } initializeNode(networkInfo: any): any { Object.keys(this.nodeObjectSet).forEach((key: string) => { - this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, () => { - return new Node(0); + this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, (v, i) => { + console.log(i / 0.5); + return new Node(i / 0.5); }); }); return this.nodeObjectSet; @@ -53,23 +54,25 @@ export class NodeHandler { // console.log(scroll, touch); this.BaseCanvas.clearCanvas(); - console.log(this.NodeRenderer); - this.drawText('렌더 시작'); + // console.log(this.NodeRenderer); + // this.drawText('렌더 시작'); Object.keys(anchorPosition).forEach((key: string) => { const layerSize = anchorPosition[key].length; Array.from({ length: layerSize }, (_, i) => { - console.log('anchoer Pos:', anchorPosition['inputLayer'][i]); + // console.log('anchoer Pos:', anchorPosition['inputLayer'][i]); + this.drawText(anchorPosition[key][i].posX, anchorPosition[key][i].posY, i); this.NodeRenderer.drawNode( anchorPosition[key][i].posX, anchorPosition[key][i].posY, + anchorPosition[key][i].angleOffset, anchorPosition[key][i].percent, ); }); console.log('그림을 그려요!'); }); - this.drawText('렌더 완전 끝남'); + // this.drawText('렌더 완전 끝남'); this.BaseCanvas.grid(50); // Object.keys(this.nodeObjectSet).forEach((key: string, index: number) => { @@ -121,10 +124,9 @@ export class NodeHandler { // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } - - drawText(text: number): void { + drawText(x, y, text: number): void { this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; - this.BaseCanvas.getCtx().fillText(`${text}`, 180, 0); + this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; } } diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 9142b32..f4700b4 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -1,7 +1,6 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/PerceptronController.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; -import { ScrollEventHandler } from '@/controller/perceptron/ScrollEventHandler.ts'; import { DATA_EVENTS, DataEvent, SCROLL_EVENTS } from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; @@ -15,6 +14,7 @@ const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; +import { findWidestLayer } from '@/controller/perceptron/utils/findWidestLayer.ts'; class PerceptronController { private NodeRenderer: INodeRenderer; private NodeHandler: INodeHandler; @@ -55,7 +55,7 @@ class PerceptronController { initializeNodeValue() { this.normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); // 원활한 시각화를 위해 입력 레이어의 크기를 compress - this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); + this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); // 시각화된 노드들의 기본값 할당 this.NodeHandler.render(0, 0); } @@ -81,6 +81,10 @@ class PerceptronController { Array.from({ length: layerSize }, (_: unknown, nodeIndex: number) => nodeIndex).map( (nodeIndex) => { + console.log(touchScroll, mouseScroll); + console.log( + `mouseScroll:${mouseScroll}, divied: ${mouseScroll / scrollDivider}`, + ); const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; @@ -96,27 +100,26 @@ class PerceptronController { // this.nodeObjectSet[key][nodeIndex].getValue(), // ); this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); - console.log('포지션 저장됨'); + const currentMatrix = this.BaseCanvas.getCtx().getTransform(); + const globalMatrix = this.BaseCanvas.setupMatrix.multiply(currentMatrix); + + const global = globalMatrix.transformPoint( + new DOMPoint(this.distanceFromCenter[layerIndex], 0), + ); + const angle = Math.atan2(globalMatrix.b, globalMatrix.a); + const angleOffset = angle * (180 / Math.PI); + + const percent = this.nodeObjectSet[key][nodeIndex].getValue(); + + this.anchorPosition[this.anchorPosKeys[layerIndex]].push({ + posX: global.x, + posY: global.y, + angleOffset: angleOffset, + percent: percent, + }); } else { this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); } - const currentMatrix = this.BaseCanvas.getCtx().getTransform(); - const globalMatrix = this.BaseCanvas.setupMatrix.multiply(currentMatrix); - - const global = globalMatrix.transformPoint( - new DOMPoint(this.distanceFromCenter[layerIndex], 0), - ); - const angle = Math.atan2(globalMatrix.b, globalMatrix.a); - const rotation = angle * (180 / Math.PI); - - const percent = this.nodeObjectSet[key][nodeIndex].getValue(); - - this.anchorPosition[this.anchorPosKeys[layerIndex]].push({ - posX: global.x, - posY: global.y, - rotation: rotation, - percent: percent, - }); this.BaseCanvas.grid(50); this.BaseCanvas.restoreState(); }, diff --git a/src/controller/perceptron/utils/findWidestLayer.ts b/src/controller/perceptron/utils/findWidestLayer.ts new file mode 100644 index 0000000..c788802 --- /dev/null +++ b/src/controller/perceptron/utils/findWidestLayer.ts @@ -0,0 +1,7 @@ +import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; + +export const findWidestLayer = (obj: networkConfig) => + Object.entries(obj).reduce((max, [key, value]) => (value > max.value ? { key, value } : max), { + key: null, + value: -Infinity, + }); diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index 83e1d85..d4bc36e 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -26,8 +26,15 @@ class NodeRenderer { this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler } - drawNode = (x: number, y: number, p: number): void => { - nodePath(this.renderCtx, { x: x, y: y, width: 15, height: 15, percent: p }); + drawNode = (x: number, y: number, angle, p: number): void => { + nodePath(this.renderCtx, { + x: x, + y: y, + width: 15, + height: 15, + angleOffset: angle, + percent: p, + }); }; } diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts index 0453eeb..fb1f575 100644 --- a/src/view/components/perceptron/shapeVector/nodePath.ts +++ b/src/view/components/perceptron/shapeVector/nodePath.ts @@ -2,9 +2,13 @@ import { INodePath } from '@/view/components/perceptron/shape/types/nodePath.ts' export const nodePath = ( ctx: CanvasRenderingContext2D, - { x, y, width, height, radius = 6, percent }: INodePath, + { x, y, width, height, radius = 6, angleOffset, percent }: INodePath, ) => { - const node: Path2D = roundedRect(x, y, width, height, radius); + 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'; @@ -15,13 +19,15 @@ export const nodePath = ( if (percent > 0) { const filledHeight = (height * percent) / 100; - const fillPath = roundedRect(x, y, filledHeight, height, radius); + 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 = ( From fa4294ed2c7a6ff808c1e1b54b54d5bc82080e83 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 25 Oct 2025 19:33:47 +0900 Subject: [PATCH 57/94] =?UTF-8?q?feat:=20=EB=85=B8=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=ED=95=9C=EA=B3=84=EA=B0=92=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../perceptron/ScrollEventHandler.ts | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index 086d60f..915faa0 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -1,9 +1,13 @@ import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +import { NETWORK_CONFIG } from '@/controller/constants/networkConfig.ts'; import eventBus from '@/controller/EventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; +import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; +import { findWidestLayer } from '@/controller/perceptron/utils/findWidestLayer.ts'; -const { degree, rotationDelta, scrollDivider } = RENDERER_CONFIG; +const { degree, rotationDelta, displayNodes, scrollDivider } = RENDERER_CONFIG; +const { inputNodes, hiddenNodes, outputNodes } = NETWORK_CONFIG; class ScrollEventHandler { private BaseCanvas: IBaseCanvas; @@ -14,6 +18,12 @@ class ScrollEventHandler { private touchDirection: number; private rotationStack: number; + private widestLayer: number; + private leftLimit: number; + private rightLimit: number; + private leftScrollLimit: boolean; + private rightScrollLimit: boolean; + constructor(BaseCanvas: IBaseCanvas) { this.BaseCanvas = BaseCanvas; this.canvasEl = BaseCanvas.getCanvas(); // perceptron base canvas @@ -22,23 +32,38 @@ class ScrollEventHandler { this.touchMove = 0; this.touchDirection = 0; + this.widestLayer = 0; + this.leftScrollLimit = true; + this.rightScrollLimit = true; + this.registerEvent(); } registerEvent() { + this.calculateScrollLimit(); this.handleMouseScroll(); this.handleTouchMove(); } + calculateScrollLimit(): void { + const normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); + const { value: widestLayer } = findWidestLayer(normalizedNetworkInfo); // 스크롤 최대치 계산을 위한 가장 큰 노드의 폭을 계산 + this.rightLimit = (widestLayer / 2 - displayNodes) * scrollDivider; + this.leftLimit = -this.rightLimit; + } + handleMouseScroll(): void { this.canvasEl.addEventListener('wheel', (e: WheelEvent) => { - if (e.deltaY > 0) { - this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / scrollDivider)); + this.rightScrollLimit = this.mouseScroll <= this.rightLimit; + this.leftScrollLimit = this.mouseScroll >= this.leftLimit; + + if (e.deltaY > 0 && this.rightScrollLimit) { + this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / (scrollDivider * 2))); this.mouseScroll += 1; this.rotationStack += 1; eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); - } else if (e.deltaY < 0) { - this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / scrollDivider)); + } else if (e.deltaY < 0 && this.leftScrollLimit) { + this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / (scrollDivider * 2))); this.mouseScroll -= 1; this.rotationStack -= 1; eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); @@ -56,14 +81,16 @@ class ScrollEventHandler { const moveX = Math.floor(touch.clientX); this.touchDirection = moveX - touchStartX; - if (this.touchDirection < 0) { - // console.log('캔버스 돌아감'); - this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / scrollDivider)); + this.rightScrollLimit = this.touchMove <= this.rightLimit; + this.leftScrollLimit = this.touchMove >= this.leftLimit; + + if (this.touchDirection < 0 && this.rightScrollLimit) { + this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / (scrollDivider * 2))); this.touchMove += 1; this.rotationStack += 1; eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); - } else if (this.touchDirection > 0) { - this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / scrollDivider)); + } else if (this.touchDirection > 0 && this.leftScrollLimit) { + this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / (scrollDivider * 2))); this.touchMove -= 1; this.rotationStack -= 1; eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); From bbaf8776dd90beccc8520c5bf82fc48d86c2149a Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 01:04:24 +0900 Subject: [PATCH 58/94] =?UTF-8?q?feat:=20perceptron=EC=9D=98=20=EA=B0=84?= =?UTF-8?q?=EC=84=A0(edge)=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 6 +- src/controller/constants/rendererConfig.ts | 4 +- src/controller/perceptron/EdgeHandler.ts | 29 ++++++++- src/controller/perceptron/NodeHandler.ts | 64 +------------------ .../perceptron/PerceptronController.ts | 19 ++---- .../perceptron/ScrollEventHandler.ts | 1 - .../perceptron/types/PerceptronController.ts | 1 + src/view/components/perceptron/BaseCanvas.ts | 11 ++-- .../components/perceptron/EdgeRenderer.ts | 19 +++++- 9 files changed, 66 insertions(+), 88 deletions(-) diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 0ea475c..d4bc028 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -22,6 +22,7 @@ import { Matrix2D } from '@/core/ops/types/OpsType.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import EdgeHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; +import EdgeRenderer from '@/view/components/perceptron/EdgeRenderer.ts'; class AppController { private $WM: IWeightManager; @@ -49,10 +50,13 @@ class AppController { nodeRenderer, perceptronBaseCanvas, }); + const edgeRenderer = new EdgeRenderer(perceptronBaseCanvas.getCtx()); + const edgeHandler = new EdgeHandler({ edgeRenderer, perceptronBaseCanvas }); const $PC = new PerceptronController({ NodeRenderer: nodeRenderer, + EdgeRenderer: edgeRenderer, NodeHandler: nodeHandler, - EdgeHandler: new EdgeHandler(), + EdgeHandler: edgeHandler, BaseCanvas: perceptronBaseCanvas, ScrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts index 98c1722..1c0bcb1 100644 --- a/src/controller/constants/rendererConfig.ts +++ b/src/controller/constants/rendererConfig.ts @@ -1,9 +1,9 @@ import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; export const RENDERER_CONFIG: rendererConfig = { - rotationDelta: 4, + rotationDelta: 2, degree: Math.PI / 180, mouseScroll: 1, - displayNodes: 30, + displayNodes: 24, scrollDivider: 4, }; diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 7a3d1c3..72a7ac2 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,30 @@ -class EdgeHandler {} +class EdgeHandler { + private EdgeRenderer: IEdgeRenderer; + private BaseCanvas: IBaseCanvas; + + constructor({ + edgeRenderer, + perceptronBaseCanvas, + }: { + edgeRenderer: IEdgeRenderer; + perceptronBaseCanvas: IBaseCanvas; + }) { + this.EdgeRenderer = edgeRenderer; + this.BaseCanvas = perceptronBaseCanvas; + } + + render(anchorPosition: any): void { + const layerKeysList = Object.keys(anchorPosition); + layerKeysList.flatMap((layerKey, index, key) => { + const current = anchorPosition[layerKey] ?? []; + const next = anchorPosition[key[index + 1]] ?? []; + return current + .flatMap((a) => next.map((b) => [a, b])) + .forEach(([a, b]) => { + this.EdgeRenderer.drawEdge(a.posX, a.posY, b.posX, b.posY); + }); + }); + } +} export default EdgeHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index cfe4017..093c745 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -28,39 +28,22 @@ export class NodeHandler { }; this.NodeRenderer = nodeRenderer; this.BaseCanvas = perceptronBaseCanvas; - console.log(perceptronBaseCanvas); - - // this.registerScrollEvent(); - - // console.log(this.nodeObjectSet); } initializeNode(networkInfo: any): any { Object.keys(this.nodeObjectSet).forEach((key: string) => { this.nodeObjectSet[key] = Array.from({ length: networkInfo[key] }, (v, i) => { - console.log(i / 0.5); - return new Node(i / 0.5); + return new Node(0); }); }); return this.nodeObjectSet; } render(anchorPosition: any): void { - // Object.keys(this.nodeObjectSet).forEach((key: string) => { - // console.log('key:', key, 'value:', this.nodeObjectSet[key]); - // }); - // const scroll = mouseScroll; - // const touch = touchScroll; - // console.log(scroll, touch); - - this.BaseCanvas.clearCanvas(); - // console.log(this.NodeRenderer); - // this.drawText('렌더 시작'); - + // this.BaseCanvas.clearCanvas(); Object.keys(anchorPosition).forEach((key: string) => { const layerSize = anchorPosition[key].length; Array.from({ length: layerSize }, (_, i) => { - // console.log('anchoer Pos:', anchorPosition['inputLayer'][i]); this.drawText(anchorPosition[key][i].posX, anchorPosition[key][i].posY, i); this.NodeRenderer.drawNode( anchorPosition[key][i].posX, @@ -69,51 +52,10 @@ export class NodeHandler { anchorPosition[key][i].percent, ); }); - console.log('그림을 그려요!'); }); - // this.drawText('렌더 완전 끝남'); this.BaseCanvas.grid(50); - // Object.keys(this.nodeObjectSet).forEach((key: string, index: number) => { - // // 각 레이어의 수를 기준으로 중앙정렬 회전 - // console.log('레이어당 노드수', this.nodeObjectSet[key].length); - // console.log('기준 노드', this.nodeObjectSet[key].length / 2); - // this.BaseCanvas.grid(300); - // this.BaseCanvas.saveState(); // 현재의 상태를 저장(레이어 기준) - // this.BaseCanvas.rotateCanvas( - // true, - // degree * 90 - (degree * rotationDelta * this.nodeObjectSet[key].length) / 2, - // ); // 캔버스를 기준으로 레이어를 가운데 정렬 - // - // const layerSize = this.nodeObjectSet[key].length; // 해당 이터레이션에서의 레이어 노드 수 - // - // const color = ['rgb(255,0,0)', 'rgb(0,255,0)', 'rgb(0,0,255)']; - // const size = [350, 380, 410]; - // this.BaseCanvas.getCtx().strokeStyle = color[index]; - // Array.from({ length: layerSize }, (_: unknown, i: number) => i).forEach((i) => { - // if ( - // i >= - // layerSize / 2 - - // displayNodes / 2 + - // (mouseScroll + touchScroll) / scrollDivider && - // i < - // layerSize / 2 + - // displayNodes / 2 + - // (mouseScroll + touchScroll) / scrollDivider - // ) { - // this.nodeRenderer.drawNode(size[index], this.nodeObjectSet[key][i].getValue()); - // this.drawText(i); - // this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - // // this.BaseCanvas.grid(size[index]); - // } else { - // this.BaseCanvas.rotateCanvas(true, degree * rotationDelta); - // } - // }); - // this.BaseCanvas.restoreState(); - // this.drawText(`${key}`); - // }); - // TODO: 각 레이별로 중심점 을 옮겨서 계층을 분리할 수 있게 작업 // TODO: 각 레이어별 돌아간 회전 Stack만큼 Canvas 역회전 // TODO: 노드에 관한 간선 연결 시각화 로직 구현 @@ -121,8 +63,6 @@ export class NodeHandler { // TODO: 반응형 설계 및 레이아웃 구현 // TODO: perceptron Canvas 위치 잡기 // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 - - // Array.from({ length: Object.keys(this.nodeObjectSet).length }, (key: string) => {}); } drawText(x, y, text: number): void { this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index f4700b4..8305ce6 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -41,7 +41,7 @@ class PerceptronController { this.BaseCanvas = BaseCanvas; this.ScrollEventHandler = ScrollEventHandler; - this.distanceFromCenter = [350, 380, 410]; + this.distanceFromCenter = [600, 630, 660]; this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; this.initializeNodeValue(); @@ -81,10 +81,6 @@ class PerceptronController { Array.from({ length: layerSize }, (_: unknown, nodeIndex: number) => nodeIndex).map( (nodeIndex) => { - console.log(touchScroll, mouseScroll); - console.log( - `mouseScroll:${mouseScroll}, divied: ${mouseScroll / scrollDivider}`, - ); const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; @@ -95,10 +91,6 @@ class PerceptronController { this.BaseCanvas.saveState(); if (displayCondition) { - // this.NodeRenderer.drawNode( - // this.distanceFromCenter[layerIndex], - // this.nodeObjectSet[key][nodeIndex].getValue(), - // ); this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); const currentMatrix = this.BaseCanvas.getCtx().getTransform(); const globalMatrix = this.BaseCanvas.setupMatrix.multiply(currentMatrix); @@ -110,7 +102,6 @@ class PerceptronController { const angleOffset = angle * (180 / Math.PI); const percent = this.nodeObjectSet[key][nodeIndex].getValue(); - this.anchorPosition[this.anchorPosKeys[layerIndex]].push({ posX: global.x, posY: global.y, @@ -126,12 +117,10 @@ class PerceptronController { ); this.BaseCanvas.restoreState(); }); + console.log('anchorPosition:', this.anchorPosition); + this.BaseCanvas.clearCanvas(); + this.EdgeHandler.render(this.anchorPosition); this.NodeHandler.render(this.anchorPosition); - // Position, Rotation 값 Object로 넘기기 - } - - calculateEdgePosition() { - // Position 값 Object로 넘기기 } renderPerceptron() {} diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index 915faa0..b2cb358 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -7,7 +7,6 @@ import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeN import { findWidestLayer } from '@/controller/perceptron/utils/findWidestLayer.ts'; const { degree, rotationDelta, displayNodes, scrollDivider } = RENDERER_CONFIG; -const { inputNodes, hiddenNodes, outputNodes } = NETWORK_CONFIG; class ScrollEventHandler { private BaseCanvas: IBaseCanvas; diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index e2d5ae5..b29b795 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -2,6 +2,7 @@ export interface IPerceptronControllerProps { NodeHandler: INodePositionHandler; EdgeHandler: INdgePositionHandler; NodeRenderer: INodeRenderer; + EdgeRenderer: IEdgeRenderer; BaseCanvas: IBasecanvas; ScrollEventHandler: IScrollEventHandler; } diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index eb9bb74..c015bac 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -22,13 +22,13 @@ class BaseCanvas { getCtx = (): CanvasRenderingContext2D => this.ctx; setupCanvas() { - this.canvas.width = 1000; + this.canvas.width = 2000; // this.canvas.height = window.innerHeight * (5 / 20); this.canvas.height = 1000; - this.ctx.fillStyle = 'rgb(186,186,186)'; + 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 / 2 }; + this.canvasCenter = { x: this.canvas.width / 2, y: this.canvas.height }; } setupRenderTransform(): void { @@ -48,10 +48,11 @@ class BaseCanvas { }; clearCanvas() { + this.saveState(); this.ctx.translate(-this.canvasCenter.x, -this.canvasCenter.y); - this.ctx.fillStyle = 'rgb(255,137,137)'; + this.ctx.fillStyle = 'rgb(255,255,255)'; this.ctx.fillRect(0, 0, this.canvasCenter.x * 2, this.canvasCenter.y * 2); - this.ctx.translate(this.canvasCenter.x, this.canvasCenter.y); + this.restoreState(); } saveState() { diff --git a/src/view/components/perceptron/EdgeRenderer.ts b/src/view/components/perceptron/EdgeRenderer.ts index 652b12b..e6da5d2 100644 --- a/src/view/components/perceptron/EdgeRenderer.ts +++ b/src/view/components/perceptron/EdgeRenderer.ts @@ -1,3 +1,20 @@ -class EdgeRenderer {} +class EdgeRenderer { + private renderCtx: CanvasRenderingContext2D; + + constructor(ctx: CanvasRenderingContext2D) { + this.renderCtx = ctx; + } + + drawEdge = (aX: number, aY: number, bX: number, bY: number): void => { + this.renderCtx.strokeStyle = '#000000'; + this.renderCtx.lineWidth = 1; + + this.renderCtx.beginPath(); + this.renderCtx.moveTo(aX, aY); + this.renderCtx.lineTo(bX, bY); + this.renderCtx.stroke(); + this.renderCtx.moveTo(aX, aY); + }; +} export default EdgeRenderer; From 06015ba64050d1c51bce51b3ac7d003134e1286b Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 01:19:27 +0900 Subject: [PATCH 59/94] =?UTF-8?q?refactor:=20=EA=B9=94=EB=81=94=ED=95=9C?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=81=ED=99=94=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?perceptron=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 각 layer의 node를 기준으로 15개의 edge만 연결되도록 수정 --- src/controller/perceptron/EdgeHandler.ts | 20 ++++++++++++++++--- .../components/perceptron/EdgeRenderer.ts | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 72a7ac2..583306e 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -15,11 +15,25 @@ class EdgeHandler { render(anchorPosition: any): void { const layerKeysList = Object.keys(anchorPosition); - layerKeysList.flatMap((layerKey, index, key) => { + + const subsetByRatio = (arr: any[], ratio: number, size: number) => { + if (!Array.isArray(arr) || arr.length === 0) return []; + const maxStart = Math.max(arr.length - size, 0); + const start = Math.floor(ratio * maxStart); + return arr.slice(start, start + size); + }; + + layerKeysList.slice(0, -1).flatMap((layerKey, index) => { const current = anchorPosition[layerKey] ?? []; - const next = anchorPosition[key[index + 1]] ?? []; + const next = anchorPosition[layerKeysList[index + 1]] ?? []; + return current - .flatMap((a) => next.map((b) => [a, b])) + .flatMap((a, aIndex) => { + const ratio = current.length <= 1 ? 0 : aIndex / (current.length - 1); + const subset = subsetByRatio(next, ratio, 15); + + return subset.map((b) => [a, b]); + }) .forEach(([a, b]) => { this.EdgeRenderer.drawEdge(a.posX, a.posY, b.posX, b.posY); }); diff --git a/src/view/components/perceptron/EdgeRenderer.ts b/src/view/components/perceptron/EdgeRenderer.ts index e6da5d2..e121e72 100644 --- a/src/view/components/perceptron/EdgeRenderer.ts +++ b/src/view/components/perceptron/EdgeRenderer.ts @@ -7,7 +7,7 @@ class EdgeRenderer { drawEdge = (aX: number, aY: number, bX: number, bY: number): void => { this.renderCtx.strokeStyle = '#000000'; - this.renderCtx.lineWidth = 1; + this.renderCtx.lineWidth = 0.3; this.renderCtx.beginPath(); this.renderCtx.moveTo(aX, aY); From 81d22c128be8ad34892be43e3c1370da8ca04dd2 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 18:30:41 +0900 Subject: [PATCH 60/94] =?UTF-8?q?test:=20=EB=8F=99=EC=9E=91=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20user-input?= =?UTF-8?q?=20canvas=20=EC=88=A8=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/styles/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/styles/main.css b/src/view/styles/main.css index efae606..60d66f2 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -21,6 +21,7 @@ } #user-input-canvas { + display: none; position: relative; z-index: 1; /*clip-path: ellipse(140% 100% at 50% 100%);*/ From cca64c9b31e214e60dd91fce08f496c71ae4ffd7 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 18:32:13 +0900 Subject: [PATCH 61/94] =?UTF-8?q?chore:=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EC=84=B8=EB=B6=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/types/nodeObjectSet.ts | 2 +- src/view/components/perceptron/{ => objectClass}/Edge.ts | 0 src/view/components/perceptron/{ => objectClass}/Node.ts | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/view/components/perceptron/{ => objectClass}/Edge.ts (100%) rename src/view/components/perceptron/{ => objectClass}/Node.ts (100%) diff --git a/src/controller/perceptron/types/nodeObjectSet.ts b/src/controller/perceptron/types/nodeObjectSet.ts index b39758f..13a09cc 100644 --- a/src/controller/perceptron/types/nodeObjectSet.ts +++ b/src/controller/perceptron/types/nodeObjectSet.ts @@ -1,4 +1,4 @@ -import {Node} from "@/view/components/perceptron/Node"; +import { Node } from '@/view/components/perceptron/objectClass/Node.ts'; export interface NodeObjectSet { inputNodes: Array; diff --git a/src/view/components/perceptron/Edge.ts b/src/view/components/perceptron/objectClass/Edge.ts similarity index 100% rename from src/view/components/perceptron/Edge.ts rename to src/view/components/perceptron/objectClass/Edge.ts diff --git a/src/view/components/perceptron/Node.ts b/src/view/components/perceptron/objectClass/Node.ts similarity index 100% rename from src/view/components/perceptron/Node.ts rename to src/view/components/perceptron/objectClass/Node.ts From a5a3df5f96095fa40f444d0b20f110ef23dc2fcf Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 18:33:58 +0900 Subject: [PATCH 62/94] =?UTF-8?q?feat:=20grid=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 6 ++ .../constants/types/rendererConfig.ts | 2 + src/controller/perceptron/GridHandler.ts | 23 ++++++++ src/controller/perceptron/NodeHandler.ts | 13 +--- .../perceptron/PerceptronController.ts | 59 +++++++++++++------ .../perceptron/types/PerceptronController.ts | 6 +- .../components/perceptron/GridRenderer.ts | 22 +++++++ 7 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 src/controller/perceptron/GridHandler.ts create mode 100644 src/view/components/perceptron/GridRenderer.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index d4bc028..fa63868 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -23,6 +23,8 @@ import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import EdgeHandler from '@/controller/perceptron/EdgeHandler.ts'; import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; import EdgeRenderer from '@/view/components/perceptron/EdgeRenderer.ts'; +import GridRenderer from '@/view/components/perceptron/GridRenderer.ts'; +import GridHandler from '@/controller/perceptron/GridHandler.ts'; class AppController { private $WM: IWeightManager; @@ -52,11 +54,15 @@ class AppController { }); const edgeRenderer = new EdgeRenderer(perceptronBaseCanvas.getCtx()); const edgeHandler = new EdgeHandler({ edgeRenderer, perceptronBaseCanvas }); + const gridRenderer = new GridRenderer(perceptronBaseCanvas.getCtx()); + const gridHandler = new GridHandler({ gridRenderer, perceptronBaseCanvas }); const $PC = new PerceptronController({ NodeRenderer: nodeRenderer, EdgeRenderer: edgeRenderer, + GridRenderer: gridRenderer, NodeHandler: nodeHandler, EdgeHandler: edgeHandler, + GridHandler: gridHandler, BaseCanvas: perceptronBaseCanvas, ScrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); diff --git a/src/controller/constants/types/rendererConfig.ts b/src/controller/constants/types/rendererConfig.ts index 06cfec4..054bfa0 100644 --- a/src/controller/constants/types/rendererConfig.ts +++ b/src/controller/constants/types/rendererConfig.ts @@ -4,4 +4,6 @@ export interface rendererConfig { mouseScroll: number; displayNodes: number; scrollDivider: number; + gridWidth: number; + layerHeight: number[]; } diff --git a/src/controller/perceptron/GridHandler.ts b/src/controller/perceptron/GridHandler.ts new file mode 100644 index 0000000..b4f278d --- /dev/null +++ b/src/controller/perceptron/GridHandler.ts @@ -0,0 +1,23 @@ +class GridHandler { + private GridRenderer: IGridRenderer; + private BaseCanvas: IBaseCanvas; + + constructor({ + gridRenderer, + perceptronBaseCanvas, + }: { + gridRenderer: IGridRenderer; + perceptronBaseCanvas: IBaseCanvas; + }) { + this.GridRenderer = gridRenderer; + this.BaseCanvas = perceptronBaseCanvas; + } + + render(gridIndex: number): void { + const gridLength: 10 | 8 = gridIndex % 2 === 0 ? 10 : 8; + this.GridRenderer.drawGrid(560, gridLength); //bottom-side gird + this.GridRenderer.drawGrid(710, -gridLength); //top-side grid + } +} + +export default GridHandler; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 093c745..82da37d 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -1,5 +1,5 @@ import { networkConfig } from '@/controller/constants/types/networkConfig.ts'; -import { Node } from '@/view/components/perceptron/Node.ts'; +import { Node } from '@/view/components/perceptron/objectClass/Node.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; import eventBus from '@/controller/eventBus.ts'; @@ -40,11 +40,9 @@ export class NodeHandler { } render(anchorPosition: any): void { - // this.BaseCanvas.clearCanvas(); Object.keys(anchorPosition).forEach((key: string) => { const layerSize = anchorPosition[key].length; Array.from({ length: layerSize }, (_, i) => { - this.drawText(anchorPosition[key][i].posX, anchorPosition[key][i].posY, i); this.NodeRenderer.drawNode( anchorPosition[key][i].posX, anchorPosition[key][i].posY, @@ -56,17 +54,8 @@ export class NodeHandler { this.BaseCanvas.grid(50); - // TODO: 각 레이별로 중심점 을 옮겨서 계층을 분리할 수 있게 작업 - // TODO: 각 레이어별 돌아간 회전 Stack만큼 Canvas 역회전 - // TODO: 노드에 관한 간선 연결 시각화 로직 구현 - // TODO: 코드 리팩토링 // TODO: 반응형 설계 및 레이아웃 구현 // TODO: perceptron Canvas 위치 잡기 // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 } - drawText(x, y, text: number): void { - this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; - this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); - this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; - } } diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 8305ce6..92313ec 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -10,7 +10,8 @@ import { NETWORK_CONFIG } from '@/controller/constants/networkConfig'; import { normalizeNetworkConfig } from '@/controller/perceptron/utils/normalizeNetworkInfo.ts'; import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; -const { rotationDelta, degree, displayNodes, scrollDivider } = RENDERER_CONFIG; +const { rotationDelta, degree, displayNodes, scrollDivider, gridWidth, layerHeight } = + RENDERER_CONFIG; import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; @@ -18,35 +19,38 @@ import { findWidestLayer } from '@/controller/perceptron/utils/findWidestLayer.t class PerceptronController { private NodeRenderer: INodeRenderer; private NodeHandler: INodeHandler; - private EdgeHandler: IEdgePositionHandler; + private EdgeHandler: IEdgeHandler; + private GridHandler: IGridHandler; private BaseCanvas: IBaseCanvas; private ScrollEventHandler: IScrollEventHandler; private normalizedNetworkInfo: any; private nodeObjectSet: NodeObjectSet; - private readonly distanceFromCenter: number[]; private anchorPosition: { inputLayer: number[]; hiddenLayer: number[]; outputLayer: number[] }; private anchorPosKeys: string[] = ['inputLayer', 'hiddenLayer', 'outputLayer']; + private widestLayerSize?: number; constructor({ NodeRenderer, NodeHandler, EdgeHandler, + GridHandler, BaseCanvas, ScrollEventHandler, }: IPerceptronControllerProps) { this.NodeRenderer = NodeRenderer; this.NodeHandler = NodeHandler; this.EdgeHandler = EdgeHandler; + this.GridHandler = GridHandler; this.BaseCanvas = BaseCanvas; this.ScrollEventHandler = ScrollEventHandler; - this.distanceFromCenter = [600, 630, 660]; this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; this.initializeNodeValue(); this.registerScrollEvent(); this.calculateNodePosition(0, 0); + this.calculateGridPosition(); eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); @@ -56,26 +60,30 @@ class PerceptronController { initializeNodeValue() { this.normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); // 원활한 시각화를 위해 입력 레이어의 크기를 compress this.nodeObjectSet = this.NodeHandler.initializeNode(this.normalizedNetworkInfo); // 시각화된 노드들의 기본값 할당 - this.NodeHandler.render(0, 0); + const { value: widestLayer } = findWidestLayer(this.normalizedNetworkInfo); // 현재 perceptron에 가장 큰 layer 크기 가져오기 + this.widestLayerSize = widestLayer; // 그리드 렌더링에 layer 크기 활용 } registerScrollEvent() { - eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => - this.calculateNodePosition(mouseScroll, 0), - ); - eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => - this.calculateNodePosition(0, touchScroll), - ); + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => { + this.calculateNodePosition(mouseScroll, 0); + this.calculateGridPosition(); + }); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => { + this.calculateNodePosition(0, touchScroll); + this.calculateGridPosition(); + }); } calculateNodePosition(mouseScroll: number, touchScroll: number) { this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; Object.keys(this.normalizedNetworkInfo).map((key: string, layerIndex: number) => { this.BaseCanvas.saveState(); - this.BaseCanvas.rotateCanvas( - true, - degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2, - ); + + const rotateCenterPosition = + degree * 90 - (degree * rotationDelta * this.normalizedNetworkInfo[key]) / 2; + const scrollDividerInterpolation = degree * rotationDelta; + this.BaseCanvas.rotateCanvas(true, rotateCenterPosition + scrollDividerInterpolation); const layerSize = this.normalizedNetworkInfo[key]; @@ -86,7 +94,6 @@ class PerceptronController { const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; const displayCondition = nodeIndex >= displayStart && nodeIndex < displayEnd; - this.BaseCanvas.grid(200); this.BaseCanvas.saveState(); @@ -96,7 +103,7 @@ class PerceptronController { const globalMatrix = this.BaseCanvas.setupMatrix.multiply(currentMatrix); const global = globalMatrix.transformPoint( - new DOMPoint(this.distanceFromCenter[layerIndex], 0), + new DOMPoint(layerHeight[layerIndex], 0), ); const angle = Math.atan2(globalMatrix.b, globalMatrix.a); const angleOffset = angle * (180 / Math.PI); @@ -111,7 +118,6 @@ class PerceptronController { } else { this.BaseCanvas.rotateCanvas(true, degree * rotationDelta * nodeIndex); } - this.BaseCanvas.grid(50); this.BaseCanvas.restoreState(); }, ); @@ -123,7 +129,22 @@ class PerceptronController { this.NodeHandler.render(this.anchorPosition); } - renderPerceptron() {} + calculateGridPosition() { + const displayGird = this.widestLayerSize; + this.BaseCanvas.saveState(); + this.BaseCanvas.rotateCanvas(true, degree - (degree * gridWidth * displayGird) / 2); + Array.from({ length: displayGird }, (_: unknown, gridIndex: number) => { + this.BaseCanvas.rotateCanvas(true, degree * gridWidth); + this.GridHandler.render(gridIndex); + }); + this.BaseCanvas.restoreState(); + } + + drawText(x: number, y: number, text: string): void { + this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; + this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); + this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; + } } export default PerceptronController; diff --git a/src/controller/perceptron/types/PerceptronController.ts b/src/controller/perceptron/types/PerceptronController.ts index b29b795..c11a555 100644 --- a/src/controller/perceptron/types/PerceptronController.ts +++ b/src/controller/perceptron/types/PerceptronController.ts @@ -1,8 +1,10 @@ export interface IPerceptronControllerProps { - NodeHandler: INodePositionHandler; - EdgeHandler: INdgePositionHandler; NodeRenderer: INodeRenderer; EdgeRenderer: IEdgeRenderer; + GridRenderer: IGridRenderer; + NodeHandler: INodeHandler; + EdgeHandler: INdgeHandler; + GridHandler: IGridHandler; BaseCanvas: IBasecanvas; ScrollEventHandler: IScrollEventHandler; } diff --git a/src/view/components/perceptron/GridRenderer.ts b/src/view/components/perceptron/GridRenderer.ts new file mode 100644 index 0000000..959d774 --- /dev/null +++ b/src/view/components/perceptron/GridRenderer.ts @@ -0,0 +1,22 @@ +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(); + } +} + +export default GridRenderer; From e453f1346cfc68a35e5fc0425c7c3b3bef423d33 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 26 Oct 2025 18:34:22 +0900 Subject: [PATCH 63/94] =?UTF-8?q?chore:=20=EC=84=A4=EB=AA=85=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/rendererConfig.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts index 1c0bcb1..0dfb5a5 100644 --- a/src/controller/constants/rendererConfig.ts +++ b/src/controller/constants/rendererConfig.ts @@ -1,9 +1,11 @@ import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; export const RENDERER_CONFIG: rendererConfig = { - rotationDelta: 2, - degree: Math.PI / 180, - mouseScroll: 1, - displayNodes: 24, - scrollDivider: 4, + rotationDelta: 2, // 노드간 기본 간격 + degree: Math.PI / 180, // 1도(radian -> degree) + mouseScroll: 1, // 마우스 스크롤 값 + displayNodes: 25, // 화면에 표시할 노드의 개수 + scrollDivider: 4, // 스크롤 n틱당 노드 한개 표시(ex. 스크롤 4번시 캔버스 회전) + gridWidth: 1, // 그리드간 간격 + layerHeight: [600, 635, 670], // 노드의 레이어당 높이(distance from center) }; From cabde9b7eec8ae3fa750dea96bf7c397167ea5d3 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:27:57 +0900 Subject: [PATCH 64/94] =?UTF-8?q?feat:=20userInputCanvas=20cliping=20UI=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../queryPipeline/DrawingEventHandler.ts | 2 +- .../components/perceptron/NodeRenderer.ts | 6 ++--- .../perceptron/shapeVector/nodePath.ts | 2 +- .../userQuery/PathTrackingCanvas.ts | 6 +++-- .../components/userQuery/UserInputCanvas.ts | 13 +++++++-- .../userQuery/uiUtils/userInputClipPath.ts | 27 +++++++++++++++++++ 6 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 src/view/components/userQuery/uiUtils/userInputClipPath.ts diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts index 70e2507..f822a67 100644 --- a/src/controller/queryPipeline/DrawingEventHandler.ts +++ b/src/controller/queryPipeline/DrawingEventHandler.ts @@ -1,4 +1,4 @@ -import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; +import BoundingBox from '@/view/components/userQuery/canvasUtils/BoundingBox.ts'; import eventBus from '../EventBus.ts'; import { DRAWING_EVENTS } from '../constants/events.ts'; diff --git a/src/view/components/perceptron/NodeRenderer.ts b/src/view/components/perceptron/NodeRenderer.ts index d4bc36e..3c9ea3e 100644 --- a/src/view/components/perceptron/NodeRenderer.ts +++ b/src/view/components/perceptron/NodeRenderer.ts @@ -26,12 +26,12 @@ class NodeRenderer { this.scrollSpeed = RENDERER_CONFIG.scrollDivider; // scrollHandler } - drawNode = (x: number, y: number, angle, p: number): void => { + drawNode = (x: number, y: number, angle: number, p: number): void => { nodePath(this.renderCtx, { x: x, y: y, - width: 15, - height: 15, + width: 13, + height: 13, angleOffset: angle, percent: p, }); diff --git a/src/view/components/perceptron/shapeVector/nodePath.ts b/src/view/components/perceptron/shapeVector/nodePath.ts index fb1f575..871e606 100644 --- a/src/view/components/perceptron/shapeVector/nodePath.ts +++ b/src/view/components/perceptron/shapeVector/nodePath.ts @@ -2,7 +2,7 @@ import { INodePath } from '@/view/components/perceptron/shape/types/nodePath.ts' export const nodePath = ( ctx: CanvasRenderingContext2D, - { x, y, width, height, radius = 6, angleOffset, percent }: INodePath, + { x, y, width, height, radius = 5, angleOffset, percent }: INodePath, ) => { const node: Path2D = roundedRect(-width / 2, -height / 2, width, height, radius); diff --git a/src/view/components/userQuery/PathTrackingCanvas.ts b/src/view/components/userQuery/PathTrackingCanvas.ts index 5111c4c..5645de8 100644 --- a/src/view/components/userQuery/PathTrackingCanvas.ts +++ b/src/view/components/userQuery/PathTrackingCanvas.ts @@ -11,8 +11,10 @@ class PathTrackingCanvas { } setupCanvas(): void { - this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight * (7 / 20); + // 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)'; diff --git a/src/view/components/userQuery/UserInputCanvas.ts b/src/view/components/userQuery/UserInputCanvas.ts index 4e80483..b23d25b 100644 --- a/src/view/components/userQuery/UserInputCanvas.ts +++ b/src/view/components/userQuery/UserInputCanvas.ts @@ -1,4 +1,5 @@ import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts'; +import { userInputClipPath } from '@/view/components/userQuery/uiUtils/userInputClipPath.ts'; class UserInputCanvas { public readonly canvas: HTMLCanvasElement | null; @@ -8,6 +9,13 @@ class UserInputCanvas { constructor() { 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(); @@ -24,8 +32,9 @@ class UserInputCanvas { }; setupCanvas(): void { - this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight * (7 / 20); + // 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); 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; +}; From 719cec38fe181b5f160fc7aa32dcf579af341677 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:29:21 +0900 Subject: [PATCH 65/94] =?UTF-8?q?chore:=20BoundingBox=20=EC=9C=A0=ED=8B=B8?= =?UTF-8?q?=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/view/components/{ => userQuery}/canvasUtils/BoundingBox.ts | 2 +- src/view/components/{canvasUtils => userQuery/types}/types.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/view/components/{ => userQuery}/canvasUtils/BoundingBox.ts (95%) rename src/view/components/{canvasUtils => userQuery/types}/types.ts (100%) diff --git a/src/view/components/canvasUtils/BoundingBox.ts b/src/view/components/userQuery/canvasUtils/BoundingBox.ts similarity index 95% rename from src/view/components/canvasUtils/BoundingBox.ts rename to src/view/components/userQuery/canvasUtils/BoundingBox.ts index bd7b1b1..f99faa1 100644 --- a/src/view/components/canvasUtils/BoundingBox.ts +++ b/src/view/components/userQuery/canvasUtils/BoundingBox.ts @@ -1,4 +1,4 @@ -import { IinitialCoords, originalObject } from './types'; +import { IinitialCoords, originalObject } from '../types/types.ts'; class BoundingBox { private readonly initialCoords: IinitialCoords; diff --git a/src/view/components/canvasUtils/types.ts b/src/view/components/userQuery/types/types.ts similarity index 100% rename from src/view/components/canvasUtils/types.ts rename to src/view/components/userQuery/types/types.ts From bf32cbee88d17d62ca72724d48a9dcdf12e3a8b4 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:31:41 +0900 Subject: [PATCH 66/94] =?UTF-8?q?design:=20UI=20layout=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/eraser.png | Bin 0 -> 958 bytes assets/left_arrow.png | Bin 0 -> 521 bytes assets/right_arrow.png | Bin 0 -> 523 bytes index.html | 47 +++++-- public/assets/icons/left_arrow.png | Bin 0 -> 521 bytes public/assets/icons/right_arrow.png | Bin 0 -> 963 bytes src/view/styles/main.css | 192 ++++++++++++++++++++++++---- 7 files changed, 204 insertions(+), 35 deletions(-) create mode 100644 assets/eraser.png create mode 100644 assets/left_arrow.png create mode 100644 assets/right_arrow.png create mode 100644 public/assets/icons/left_arrow.png create mode 100644 public/assets/icons/right_arrow.png diff --git a/assets/eraser.png b/assets/eraser.png new file mode 100644 index 0000000000000000000000000000000000000000..19ea8881d3c46360f01b8b64cd0ebc0bda5d2fae GIT binary patch literal 958 zcmV;v13~@~0drDELIAGL9O(c600d`2O+f$vv5yPA>lLbRas2JJ_5?cVG)y_wc>;?*C>qW(?fz ze!nCO*+N7B000000Ay!Fjl#@3{pI-hy4h@A38X^_->T)iTJ~!B$IBela(@yIh=CIN zup<2uPh)5V!Wq|Oy;g@ZU~33OiM!DPuVvhFZV0%e<@@e(5JC(R$VUjSB$SU3Yy`?5 z)bhf9AcQ#q@*jD2h{=A3stCd0DSuU5%(1#4g)l}4Zln~&2w|NZf*2tzlT!>MgmX#= zVT3SIQUv!NLJb0HsG$foHC|IKS*Wq$8f!{I&5hPvTNETTD51t6BsC_fRwpDj)JzDH z8xbEu3u}?s9$JuZ5Z6mf@(p5o5>sdZp1lp6`@>%?%QtxTc%WSLAm8BG>jR~xC;0}C zp2wfrQYw0sZ}8~-j#ALGe1mpiV^6Vx{C9ZzK-g;k6cfllho`G_Yjjpzh>bq zfKL>FmN_2Vrh8SF%hB@v9bJRxn%~`4;NT&A7j%XEE<)hx6WFUzC=uj09fG5uuu~Eo zPkxgjc=)*|U)cX>2d5$~v-24{=une8Iz094_0f0mU8u+&Lh$TyV4o9lJpDyCI1auj zu;mZ}dfWMoKTh!EF?d(kWF_y3r(I99!MULjvh<>U=|}9rxq%R}^}zQ3J`yxI3l1S` z?G6+Cq(E3;2)X53_y2SyEGUHRwW=Q9wxn)yUVyY#obfA_T7Rb;#+x z%@#u78s(o9cPLd@4l!J_d|lvpNIH}%Fr^@_U4AWvrkJ0l$gic~nDetV`GyukV5#yA zl0#tW@(mJ0C|z@O1cuO#e1otMiqx2#hC+}h-_Sq^a^)KYhag|RL0|~g$d7g-H{B4f z8(ZdCEx(p-CKvTidI({aqgrm~Ap~6|c?mW6U-j^f>{kl*$hcDeoF3iYlHJS`AuOW;ICCC#7c`p)yQ7ah&!0$sJ6miCZ=`<_UP5mPx3rLcCye6%(1Lsb g0000006?Go2l{6cgPXYV@Bjb+07*qoM6N<$f=QvM8~^|S literal 0 HcmV?d00001 diff --git a/assets/left_arrow.png b/assets/left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2f658335268f6acb1903f4cc13ecc9cf23e363 GIT binary patch literal 521 zcmV+k0`~ohP)@~0drDELIAGL9O(c600d`2O+f$vv5yPa;*xr?PrEi8A@POXK zAX~e#CPBRSAAR5dS!?|?O*=SL6-Dt=*Y(L7><61&(Ds4;7!xi_##>$1|BUC3y%_z zfCmdm!Gi=O;cNkEI7>hSP8QIDlLR#3n*!SKRe==nMS)cCmjWr_PXejo4+1&hH-TL6 zSpbU$fdA)!V;sPLbHQf;fPWF*rvje^aB!0`C_wpEJLlY?EK6^M<6v$6ZD?mfMZ)3Q z9Dm#gz!1p3sFvqr<+k~iI=zIloE>zl2j6_OR^Lan~PFy3DUA$Q-WNW zXi2b2GYtuLaVjklRGv#p1UD$8B%)e05)#o(s#pn;HeHN_Sf?~pLbO%OmB9T1@n$ts z;;7QDXGrMm7jPt0_lyV@~0drDELIAGL9O(c600d`2O+f$vv5yPhm2H)2(&+}{7b*&OCjp3gg;Z&8@ z`drtwQG%s0{LQjm9HH?wIQ9`m5Wwdeiy<`L{qM?s7Q_&`@B1s=?METtwm=7^gHwpd z=hz-mxPNm50v=c7M!?p}oCw(4kP88}jX4nD+CduO1V|&i0FnrAfD|GKAb|)5a3i7s zoQP-u7a|G3fk+0VB3J+^2sS_r0-K)`DEMh7Z`2oY*cwXgEEF)RVBBftYZ%FQSz2WI$?o5`ZH-8o(7E1>g)125^T5 z0VKk`0aD>!0LgGCKswwC$PvB^$Q8Z`$Qk|w$Q}L)f= zz%efn4PODu{U^2P=*BfK5T6^_<^_3%R`lDP8U$Q!Zo&Av&^qaGsvqhZQWYUI3r+w4 N002ovPDHLkV1iX4&71%L literal 0 HcmV?d00001 diff --git a/index.html b/index.html index b88ec5e..88d2fbc 100644 --- a/index.html +++ b/index.html @@ -13,19 +13,42 @@
-
-
-
-
-
-
- +
NEURAL NETWORK
+
+
+
+
+
+
+
+
+ arrow_icon +
+
+
+
+ arrow_icon +
+
+
+ +
+ +
+ + + + + + + + +
+
-
- - -
-
+
\ No newline at end of file diff --git a/public/assets/icons/left_arrow.png b/public/assets/icons/left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..2a2f658335268f6acb1903f4cc13ecc9cf23e363 GIT binary patch literal 521 zcmV+k0`~ohP)@~0drDELIAGL9O(c600d`2O+f$vv5yPa;*xr?PrEi8A@POXK zAX~e#CPBRSAAR5dS!?|?O*=SL6-Dt=*Y(L7><61&(Ds4;7!xi_##>$1|BUC3y%_z zfCmdm!Gi=O;cNkEI7>hSP8QIDlLR#3n*!SKRe==nMS)cCmjWr_PXejo4+1&hH-TL6 zSpbU$fdA)!V;sPLbHQf;fPWF*rvje^aB!0`C_wpEJLlY?EK6^M<6v$6ZD?mfMZ)3Q z9Dm#gz!1p3sFvqr<+k~iI=zIloE>zl2j6_OR^Lan~PFy3DUA$Q-WNW zXi2b2GYtuLaVjklRGv#p1UD$8B%)e05)#o(s#pn;HeHN_Sf?~pLbO%OmB9T1@n$ts z;;7QDXGrMm7jPt0_lyV@~0drDELIAGL9O(c600d`2O+f$vv5yP`c* zC#h5_HrqUKPEM4~Lc2|0ERusm6kH#=yz+fRFYdwiIY+z7Bwh<7@j&K((+w|Ig9 z?p44z&IXvbeBmvCd8;n!fbH(?`rU5#mJG;Q_aTbu8A`z~lB#wt z60d9IN;5SR&lr-KTO8?N{qcC5t=H=!D^oS(sd&bCh2c=@mr6$Nk*3%yQDlGa@9)1S z;fx{2awxKoD6;>^2GmRdD*}v5#YaiJ#K>bq1z>+o`lS&lBC|$r42sB{k!zKND8)5$ zD^Nu7jNBR&OV!9J0J93kQuRwHZ#2a=t;-=3! zR63pJPT_%sR)ob-w3<`A6@VGK!9b!!M_jp9tDRVzso)!QNeaZ@x(9n{uD4fDjOhq0R33JU<`^AdN>@4yHReb@%pl=8%>Y_0Vs_b zVjy1<&p_Pka|gTX^Vo?FD>>sY2kSQ6mUM@NJ_!S2#d>_I35^w2s8lN7BI8IyrV`p1 zlxjjlY%w{^(7iBTEztwZ;8w(!#78A`n8bDsgK8d-e@$66jO;1AmxN+%K(sgFMgd~w8O#AAzY!~kwDx7;UD-6 zpe7m8v(=~D1L>@=wSRYZ@EQpnrYRI^(6xeP^bwm!+xnXJ3bd{VG75#fIK@Db zfx4EVBUfk}MP19#5zkaB0?b-MhsnjR?YbT(ziA+r$D5+d8(o|~*e%Hc=nuVu=5@;V l{p9xc_6H>MqwUodrT-sh0ii&4CJ_Jt002ovPDHLkV1f;-v`qj2 literal 0 HcmV?d00001 diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 60d66f2..ab0fbb3 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -7,43 +7,189 @@ height: 100vh; overflow: hidden; } - -#canvas-unit { - position: absolute; - bottom: 0; +header { + display: block; + position: sticky; + top: 0; left: 0; + + z-index: 10; + + width: 100%; + height: 60px; + background: #000; } -#user-input-section { +main { + display: block; + width: 100%; + height: 100%; position: relative; - /*display: flex;*/ - /*justify-content: end;*/ } -#user-input-canvas { - display: none; +#query-result { + display: block; + position: relative; + width: 100%; + height: 35%; + /*background: #757575;*/ + z-index: 5; +} + +#canvas-unit { + position: relative; + display: block; + width: 100%; + height: 65%; + z-index: 2; + + #position-aligner { + position: absolute; + width: 100%; + height: 100%; + + top: 85%; + left: 50%; + transform: translate(-50%, -50%); + } +} +.indicator { + width: 50px; + height: 110px; + position: fixed; + + bottom: 550px; + display: block; + z-index: 15; + + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(5px); + +} +#left-indicator{ + left: -15px; + transform: rotate(-10deg); + + -webkit-mask-image: linear-gradient(to right, white 70%, transparent 100%); + mask-image: linear-gradient(to right, white 70%, transparent 100%); +} +#right-indicator { + right: -15px; + transform: rotate(10deg); + + -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; - /*clip-path: ellipse(140% 100% at 50% 100%);*/ - clip-path: ellipse(100% 100% at 50% 100%); } -#user-input-section > #clear { + +#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; z-index: 2; - top: 30px; - right: 10px + width: 100%; + height: 100%; + clip-path: url(#arc-clip); } -#perceptron-section { - /*position: relative;*/ + +svg{ + display: block; +} + +#clear { position: absolute; - bottom: -45px; - left: 0; + z-index: 10; + + bottom: 480px; + right: 10px; - /*width: 100%;*/ - /*#perceptron {*/ - /* width: 100%;*/ - /* height: 100%;*/ - /*}*/ + width: 30px; + height: 30px; + + background: none; + border: none; + img { + display: block; + width: 25px; + + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + + } } + +@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); + } +} + From 131a5117fb071e2930fe3a576bc35514ac9eff36 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:32:33 +0900 Subject: [PATCH 67/94] =?UTF-8?q?refactor:=20=ED=8D=BC=EC=85=89=ED=8A=B8?= =?UTF-8?q?=EB=A1=A0=20=EA=B8=B0=EB=B3=B8=20=EB=A0=8C=EB=8D=94=EB=A7=81=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/constants/rendererConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts index 0dfb5a5..33a91fe 100644 --- a/src/controller/constants/rendererConfig.ts +++ b/src/controller/constants/rendererConfig.ts @@ -1,10 +1,10 @@ import { rendererConfig } from '@/controller/constants/types/rendererConfig.ts'; export const RENDERER_CONFIG: rendererConfig = { - rotationDelta: 2, // 노드간 기본 간격 + rotationDelta: 1.7, // 노드간 기본 간격 degree: Math.PI / 180, // 1도(radian -> degree) mouseScroll: 1, // 마우스 스크롤 값 - displayNodes: 25, // 화면에 표시할 노드의 개수 + displayNodes: 29, // 화면에 표시할 노드의 개수 scrollDivider: 4, // 스크롤 n틱당 노드 한개 표시(ex. 스크롤 4번시 캔버스 회전) gridWidth: 1, // 그리드간 간격 layerHeight: [600, 635, 670], // 노드의 레이어당 높이(distance from center) From 812d0cda3e0ef2d79d7aa67f668ff4e8da4a375c Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:33:15 +0900 Subject: [PATCH 68/94] =?UTF-8?q?refactor:=20=EA=B0=81=20=EB=85=B8?= =?UTF-8?q?=EB=93=9C=EA=B0=84=20edge=EC=9D=98=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EC=88=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/EdgeHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index 583306e..a44feba 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -30,7 +30,7 @@ class EdgeHandler { return current .flatMap((a, aIndex) => { const ratio = current.length <= 1 ? 0 : aIndex / (current.length - 1); - const subset = subsetByRatio(next, ratio, 15); + const subset = subsetByRatio(next, ratio, 13); return subset.map((b) => [a, b]); }) From b995c5bd4a659fab8caaa355b25562e128a8f045 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:34:28 +0900 Subject: [PATCH 69/94] =?UTF-8?q?feat:=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/GridHandler.ts | 34 ++++++++++++++++++- .../perceptron/PerceptronController.ts | 9 ++--- src/view/components/perceptron/BaseCanvas.ts | 8 ----- .../components/perceptron/GridRenderer.ts | 6 ++++ 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/controller/perceptron/GridHandler.ts b/src/controller/perceptron/GridHandler.ts index b4f278d..6e37b0b 100644 --- a/src/controller/perceptron/GridHandler.ts +++ b/src/controller/perceptron/GridHandler.ts @@ -1,3 +1,6 @@ +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { degree } = RENDERER_CONFIG; + class GridHandler { private GridRenderer: IGridRenderer; private BaseCanvas: IBaseCanvas; @@ -13,11 +16,40 @@ class GridHandler { this.BaseCanvas = perceptronBaseCanvas; } - render(gridIndex: number): void { + renderLayout(anchorPosition: any): void { + this.GridRenderer.drawArc(710); + + const referenceIndex = 3; + this.drawText( + anchorPosition.outputLayer[referenceIndex].posX + 10, + anchorPosition.outputLayer[referenceIndex].posY - 90, + anchorPosition.outputLayer[referenceIndex].angleOffset, + 'Input Nodes', + ); + this.drawText( + anchorPosition.outputLayer[referenceIndex].posX + 10, + anchorPosition.outputLayer[referenceIndex].posY + 12, + anchorPosition.outputLayer[referenceIndex].angleOffset, + 'Output Nodes', + ); + } + renderGrid(gridIndex: number): void { const gridLength: 10 | 8 = gridIndex % 2 === 0 ? 10 : 8; this.GridRenderer.drawGrid(560, gridLength); //bottom-side gird this.GridRenderer.drawGrid(710, -gridLength); //top-side grid } + + drawText(x: number, y: number, angleOffset: number, text: string): void { + this.BaseCanvas.saveState(); + this.BaseCanvas.moveCanvas(x, y); + this.BaseCanvas.rotateCanvas(true, ((angleOffset * Math.PI) / 180) * 1.01); + this.BaseCanvas.rotateCanvas(true, degree * 90); + this.BaseCanvas.getCtx().fillStyle = 'rgb(133,133,133)'; + // this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); + this.BaseCanvas.getCtx().fillText(`${text}`, 0, 0); + this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; + this.BaseCanvas.restoreState(); + } } export default GridHandler; diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 92313ec..40f648a 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -135,15 +135,10 @@ class PerceptronController { this.BaseCanvas.rotateCanvas(true, degree - (degree * gridWidth * displayGird) / 2); Array.from({ length: displayGird }, (_: unknown, gridIndex: number) => { this.BaseCanvas.rotateCanvas(true, degree * gridWidth); - this.GridHandler.render(gridIndex); + this.GridHandler.renderGrid(gridIndex); }); this.BaseCanvas.restoreState(); - } - - drawText(x: number, y: number, text: string): void { - this.BaseCanvas.getCtx().fillStyle = 'rgb(0, 0, 0)'; - this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); - this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; + this.GridHandler.renderLayout(this.anchorPosition); } } diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index c015bac..c41bd71 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -62,14 +62,6 @@ class BaseCanvas { restoreState() { this.ctx.restore(); } - - grid(distance: number): void { - this.ctx.beginPath(); - this.ctx.moveTo(0, 0); - this.ctx.lineTo(distance, 0); - this.ctx.stroke(); - this.ctx.moveTo(0, 0); - } } export default BaseCanvas; diff --git a/src/view/components/perceptron/GridRenderer.ts b/src/view/components/perceptron/GridRenderer.ts index 959d774..cc43971 100644 --- a/src/view/components/perceptron/GridRenderer.ts +++ b/src/view/components/perceptron/GridRenderer.ts @@ -17,6 +17,12 @@ class GridRenderer { 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; From 5b8dbc6d33db977d66c08c12bcec4e7397c28719 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:35:02 +0900 Subject: [PATCH 70/94] =?UTF-8?q?refactor:=20=ED=8D=BC=EC=85=89=ED=8A=B8?= =?UTF-8?q?=EB=A1=A0=20=EC=B5=9C=EB=8C=80=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=B2=94=EC=9C=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/ScrollEventHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index b2cb358..ce2436c 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -47,7 +47,7 @@ class ScrollEventHandler { calculateScrollLimit(): void { const normalizedNetworkInfo = normalizeNetworkConfig(NETWORK_CONFIG); const { value: widestLayer } = findWidestLayer(normalizedNetworkInfo); // 스크롤 최대치 계산을 위한 가장 큰 노드의 폭을 계산 - this.rightLimit = (widestLayer / 2 - displayNodes) * scrollDivider; + this.rightLimit = (widestLayer / 2 - displayNodes) * scrollDivider * 1.25; this.leftLimit = -this.rightLimit; } From 5ab0a786dc8717601b5baa01fb1e8853fe202058 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 01:36:38 +0900 Subject: [PATCH 71/94] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20preitter=20=EC=A0=81=EC=9A=A9,=20?= =?UTF-8?q?=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev/train/Tester.js | 34 +++++++++++-------- src/controller/perceptron/NodeHandler.ts | 6 ---- .../queryPipeline/QueryProcessController.ts | 2 +- src/view/components/userQuery/AlignCanvas.ts | 2 +- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/dev/train/Tester.js b/dev/train/Tester.js index 2fc5f4f..195798d 100644 --- a/dev/train/Tester.js +++ b/dev/train/Tester.js @@ -1,22 +1,26 @@ -import {Trainer} from "./Trainer.js"; +import { Trainer } from './Trainer.js'; // data sets -import {mnistTrainData} from "./trainData/mnist/mnistTrainData.js"; -import {mnistTestData} from "./trainData/mnist/mnistTestData.js"; -import {mnistTrainData_large} from "./trainData/mnist/mnistTraindata_large.js"; - -// train utils -import {normalizeInputs, oneHotEncodeLabels} from "./utils/trainDataHandler.js"; -import {saveWeightsAsJson} from "./utils/saveWeightsAsJson.js"; +import { mnistTrainData } from './trainData/mnist/mnistTrainData.js'; +import { mnistTestData } from './trainData/mnist/mnistTestData.js'; +import { mnistTrainData_large } from './trainData/mnist/mnistTraindata_large.js'; +// train canvasUtils +import { normalizeInputs, oneHotEncodeLabels } from './utils/trainDataHandler.js'; +import { saveWeightsAsJson } from './utils/saveWeightsAsJson.js'; // export const tester = () => { - const networkConfig = {inputNodes: 784, hiddenNodes: 200, outputNodes: 10, learningRate: 0.15}; + const networkConfig = { + inputNodes: 784, + hiddenNodes: 200, + outputNodes: 10, + learningRate: 0.15, + }; const $NN = new Trainer({ inputNodes: networkConfig.inputNodes, hiddenNodes: networkConfig.hiddenNodes, outputNodes: networkConfig.outputNodes, - learningRate: networkConfig.learningRate + learningRate: networkConfig.learningRate, }); const inputs = normalizeInputs(mnistTrainData); @@ -25,16 +29,16 @@ export const tester = () => { inputs.map((array, idx) => { $NN.train(array, targets[idx]); if (idx % 5000 === 0) console.log(`Training progress: ${idx}/60000`); - console.log("✅ training complete!"); + console.log('✅ training complete!'); }); - for (let i = 0; i<9; i++){ + for (let i = 0; i < 9; i++) { console.log('Query', mnistTestData[i][0]); console.log($NN.query(normalizeInputs(mnistTestData)[i])); } saveWeightsAsJson($NN.W_inputToHidden, $NN.W_hiddenToOutput); - console.log("✅ Weight Downloaded!"); -} + console.log('✅ Weight Downloaded!'); +}; -export default tester; \ No newline at end of file +export default tester; diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 82da37d..009589b 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -51,11 +51,5 @@ export class NodeHandler { ); }); }); - - this.BaseCanvas.grid(50); - - // TODO: 반응형 설계 및 레이아웃 구현 - // TODO: perceptron Canvas 위치 잡기 - // TODO: 데이터 종속성과 SSoT와 관련한 모듈 아키텍쳐 고민 작성해보기 } } diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index eddbb2f..3ff617b 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -13,7 +13,7 @@ import { import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; import { IDataStore } from '../types/DataStore'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; -import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts'; +import BoundingBox from '@/view/components/userQuery/canvasUtils/BoundingBox.ts'; class QueryProcessController { private readonly userInputCanvas: ICanvasBase & IUserInputCanvas; diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts index 691f45c..25cbef0 100644 --- a/src/view/components/userQuery/AlignCanvas.ts +++ b/src/view/components/userQuery/AlignCanvas.ts @@ -1,4 +1,4 @@ -import BoundingBox from '../canvasUtils/BoundingBox.ts'; +import BoundingBox from '@/view/components/userQuery/canvasUtils/BoundingBox.ts'; class AlignCanvas { public readonly canvas: HTMLCanvasElement | null; From e140df5537e97f2d482e0c42358224e28f09b944 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 02:03:09 +0900 Subject: [PATCH 72/94] =?UTF-8?q?feat:=20=EC=9B=B9=20=ED=8F=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20header=20title=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 5 ++++- src/view/styles/main.css | 13 ++++++++++++- src/view/styles/settings.css | 10 ++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 88d2fbc..59159af 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,10 @@
-
NEURAL NETWORK
+
+

Neural Network

+

visualization v1.0

+
diff --git a/src/view/styles/main.css b/src/view/styles/main.css index ab0fbb3..ef53a0c 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -17,7 +17,18 @@ header { width: 100%; height: 60px; - background: #000; + + 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 { diff --git a/src/view/styles/settings.css b/src/view/styles/settings.css index 9650093..210a1ab 100644 --- a/src/view/styles/settings.css +++ b/src/view/styles/settings.css @@ -1,7 +1,17 @@ +@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 { From 2fc6162ea3e77af52817ec0c498b5ea3ebf509d6 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 23:48:34 +0900 Subject: [PATCH 73/94] =?UTF-8?q?refactor:=20DataStore=EB=A5=BC=20?= =?UTF-8?q?=EC=8B=B1=EA=B8=80=ED=86=A4=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=8F=20type=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 3 +-- src/controller/DataStore.ts | 10 ++++++++-- .../queryPipeline/types/QueryProcessController.ts | 2 -- src/controller/types/DataStore.ts | 11 ++++++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index fa63868..8de4749 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -34,7 +34,7 @@ class AppController { async initialize() { const $WM = new WeightManager(NETWORK_CONFIG); const $NN = new NeuralNetworkBase(await $WM.getWeights()); - const $DS = new DataStore(); + const $DS = DataStore; new DrawingEventHandler(); const $QC = new QueryProcessController({ userInputCanvas: new UserInputCanvas(), @@ -42,7 +42,6 @@ class AppController { alignCanvas: new AlignCanvas(), resizeCanvas: new ResizeCanvas(), $NN: $NN, - $DS: $DS, }); const perceptronBaseCanvas = new BaseCanvas(); diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts index 556ab6c..b79635e 100644 --- a/src/controller/DataStore.ts +++ b/src/controller/DataStore.ts @@ -3,6 +3,7 @@ import { DATA_EVENTS } from './constants/events.js'; import { nodeState } from './types/DataStore'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; +import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; class DataStore { private queryInfo: number[] | null = null; @@ -27,6 +28,11 @@ class DataStore { setNodeState(nodeState: nodeState): void { if (nodeState === null) throw new Error('No node state found'); this.nodeState = nodeState; + eventBus.emit(DATA_EVENTS.NODE_CHANGED, { + inputs: nodeState.inputs, + hiddenOutputs: nodeState.hiddenOutputs, + finalOutputs: nodeState.finalOutputs, + }); } getNodeState(): nodeState { return this.nodeState; @@ -37,7 +43,7 @@ class DataStore { this.queryResult = queryResult; eventBus.emit(DATA_EVENTS.RESULT_CHANGED, queryResult); } - getQueryResult() { + getQueryResult(): Matrix2D | null { return this.queryResult; } @@ -48,4 +54,4 @@ class DataStore { } } -export default DataStore; +export default new DataStore(); diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts index e43c0bb..585e323 100644 --- a/src/controller/queryPipeline/types/QueryProcessController.ts +++ b/src/controller/queryPipeline/types/QueryProcessController.ts @@ -6,7 +6,6 @@ import { IResizeCanvas, } from '@/view/components/userQuery/types/canvas'; import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase'; -import { IDataStore } from '@/controller/types/DataStore'; export interface IQueryProcessControllerProps { userInputCanvas: ICanvasBase & IUserInputCanvas; @@ -14,5 +13,4 @@ export interface IQueryProcessControllerProps { alignCanvas: ICanvasBase & IAlignCanvas; resizeCanvas: ICanvasBase & IResizeCanvas; $NN: INeuralNetworkBase; - $DS: IDataStore; } diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts index cff00ae..21bc2fb 100644 --- a/src/controller/types/DataStore.ts +++ b/src/controller/types/DataStore.ts @@ -1,18 +1,19 @@ import { Matrix2D } from '@/core/ops/types/OpsType.ts'; +import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; export interface IDataStore { setQueryInfo(queryInfo: number[]): void; getQueryInfo(): number[]; - setNodeState(nodeState: nodeState): void; - getNodeState(): nodeState; + setNodeState(nodeState: NodeObjectSet): void; + getNodeState(): NodeObjectSet; setQueryResult(queryResult: Matrix2D): void; getQueryResult(): Matrix2D; } export interface nodeState { - inputNodes: number[]; - hiddenNodes: number[]; - outputNodes: number[]; + inputs: number[]; + hiddenOutputs: number[][]; + finalOutputs: number[][]; } From a16a31b625d1a4b92407771ebc0eb3adb0960093 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 2 Nov 2025 23:50:03 +0900 Subject: [PATCH 74/94] =?UTF-8?q?feat:=20perceptron=20=EC=8B=9C=EA=B0=81?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 22 +++++++++++++++++++ .../perceptron/PerceptronController.ts | 21 +++++++++++++----- .../queryPipeline/QueryProcessController.ts | 11 ++++++---- src/core/NeuralNetworkBase.ts | 8 ++++++- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 009589b..04904bb 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -5,6 +5,8 @@ import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; import eventBus from '@/controller/eventBus.ts'; import { SCROLL_EVENTS } from '@/controller/constants/events.ts'; import { nodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; +import { nodeState } from '@/controller/types/DataStore.ts'; +import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; export class NodeHandler { private readonly nodeObjectSet: nodeObjectSet; @@ -39,6 +41,26 @@ export class NodeHandler { return this.nodeObjectSet; } + updateNode(changedNodeState: nodeState): nodeObjectSet { + const { inputs, ...rest } = changedNodeState; + const compressedChangedNodeState = { inputs: compressArr(inputs), ...rest }; + + const keys: string[] = Object.keys(compressedChangedNodeState); + keys.forEach((key: string) => { + compressedChangedNodeState[key].flatMap((e: number, i: number) => { + if (key === 'inputs') this.nodeObjectSet.inputNodes[i].setValue(e * 100); + if (key === 'hiddenOutputs') this.nodeObjectSet.hiddenNodes[i].setValue(e * 100); + if (key === 'finalOutputs') this.nodeObjectSet.outputNodes[i].setValue(e * 100); + }); + }); + return this.nodeObjectSet; + // TODO: perceptron 렌더링 리팩토링 + // TODO: nodeObjectSet 이중 구조 바꾸기 + // TODO: 인덱스 참조 문제 디버깅 + // TODO: ScrollEventHandler 싱글톤으로 변경하기 + // TODO: 터치 엣지 케이스 찾기 + } + render(anchorPosition: any): void { Object.keys(anchorPosition).forEach((key: string) => { const layerSize = anchorPosition[key].length; diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 40f648a..1bc960a 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -16,7 +16,10 @@ import BaseCanvas from '@/view/components/perceptron/BaseCanvas.ts'; import { NodeObjectSet } from '@/controller/perceptron/types/nodeObjectSet.ts'; import { findWidestLayer } from '@/controller/perceptron/utils/findWidestLayer.ts'; +import { IDataStore, nodeState } from '@/controller/types/DataStore.ts'; +import DataStore from '@/controller/DataStore.ts'; class PerceptronController { + private $DS: IDataStore; private NodeRenderer: INodeRenderer; private NodeHandler: INodeHandler; private EdgeHandler: IEdgeHandler; @@ -38,6 +41,7 @@ class PerceptronController { BaseCanvas, ScrollEventHandler, }: IPerceptronControllerProps) { + this.$DS = DataStore; this.NodeRenderer = NodeRenderer; this.NodeHandler = NodeHandler; this.EdgeHandler = EdgeHandler; @@ -51,10 +55,7 @@ class PerceptronController { this.registerScrollEvent(); this.calculateNodePosition(0, 0); this.calculateGridPosition(); - - eventBus.on(DATA_EVENTS.NODE_CHANGED, ({ inputs, hiddenOutputs, finalOutputs }) => { - console.log('nodes 출력:', inputs, compressArr(inputs), hiddenOutputs, finalOutputs); - }); + this.updatePerceptron(); } initializeNodeValue() { @@ -123,7 +124,6 @@ class PerceptronController { ); this.BaseCanvas.restoreState(); }); - console.log('anchorPosition:', this.anchorPosition); this.BaseCanvas.clearCanvas(); this.EdgeHandler.render(this.anchorPosition); this.NodeHandler.render(this.anchorPosition); @@ -140,6 +140,17 @@ class PerceptronController { this.BaseCanvas.restoreState(); this.GridHandler.renderLayout(this.anchorPosition); } + + updatePerceptron() { + eventBus.on(DATA_EVENTS.NODE_CHANGED, (changedNodeState: nodeState) => { + this.nodeObjectSet = this.NodeHandler.updateNode(changedNodeState); + this.calculateNodePosition( + this.ScrollEventHandler.mouseScroll, + this.ScrollEventHandler.touchMove, + ); + this.calculateGridPosition(); + }); + } } export default PerceptronController; diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index 3ff617b..f72d7cd 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -1,4 +1,5 @@ import eventBus from '../EventBus.js'; +import DataStore from '../DataStore.js'; import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js'; import { pixelExtractor } from './pixelExtractor.js'; import { throttle } from './throttle'; @@ -20,8 +21,8 @@ class QueryProcessController { private readonly trackingCanvas: ICanvasBase & IPathTrackingCanvas; private readonly alignCanvas: ICanvasBase & IAlignCanvas; private readonly resizeCanvas: ICanvasBase & IResizeCanvas; - private readonly $NN: INeuralNetworkBase; private readonly $DS: IDataStore; + private readonly $NN: INeuralNetworkBase; private readonly queryFrequencyMs: number; //TODO: Canvas clear시 Skeleton 에니메이션 적용하기 @@ -31,7 +32,6 @@ class QueryProcessController { alignCanvas, resizeCanvas, $NN, - $DS, }: IQueryProcessControllerProps) { this.userInputCanvas = userInputCanvas; this.trackingCanvas = trackingCanvas; @@ -39,7 +39,7 @@ class QueryProcessController { this.resizeCanvas = resizeCanvas; this.$NN = $NN; - this.$DS = $DS; + this.$DS = DataStore; this.registerDrawingEvent(); this.query(); this.queryFrequencyMs = 200; @@ -74,7 +74,10 @@ class QueryProcessController { query() { const throttleQuery = throttle((inputs) => { const result: Matrix2D = this.$NN.query(inputs); - if (result) eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); + if (result) { + // eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); + DataStore.setQueryResult(result); + } }, this.queryFrequencyMs); eventBus.on(DATA_EVENTS.QUERY_CHANGED, (inputs: number[]) => throttleQuery(inputs)); diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts index a5a2c67..db02b9a 100644 --- a/src/core/NeuralNetworkBase.ts +++ b/src/core/NeuralNetworkBase.ts @@ -5,6 +5,7 @@ import { DATA_EVENTS } from '@/controller/constants/events.js'; import { weights } from '@/controller/types/weights'; import { Matrix2D } from './ops/types/OpsType'; +import DataStore from '@/controller/DataStore.ts'; class NeuralNetworkBase { private readonly W_inputToHidden: number[][] | null; @@ -33,7 +34,12 @@ class NeuralNetworkBase { query(inputs: number[]): Matrix2D { const { hiddenOutputs, finalOutputs } = this.feedForward(inputs); - eventBus.emit(DATA_EVENTS.NODE_CHANGED, { inputs, hiddenOutputs, finalOutputs }); + DataStore.setNodeState({ + inputs: inputs, + hiddenOutputs: hiddenOutputs, + finalOutputs: finalOutputs, + }); + // eventBus.emit(DATA_EVENTS.NODE_CHANGED, { inputs, hiddenOutputs, finalOutputs }); return finalOutputs; } } From 833d95e4cd9160d9313c0d4f8cc389044b7eae27 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:37:37 +0900 Subject: [PATCH 75/94] =?UTF-8?q?fix:=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=8B=9C=20=EB=82=98=ED=83=80?= =?UTF-8?q?=EB=82=AC=EB=8D=98=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/GridHandler.ts | 32 ++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/controller/perceptron/GridHandler.ts b/src/controller/perceptron/GridHandler.ts index 6e37b0b..68e082a 100644 --- a/src/controller/perceptron/GridHandler.ts +++ b/src/controller/perceptron/GridHandler.ts @@ -19,19 +19,25 @@ class GridHandler { renderLayout(anchorPosition: any): void { this.GridRenderer.drawArc(710); - const referenceIndex = 3; - this.drawText( - anchorPosition.outputLayer[referenceIndex].posX + 10, - anchorPosition.outputLayer[referenceIndex].posY - 90, - anchorPosition.outputLayer[referenceIndex].angleOffset, - 'Input Nodes', - ); - this.drawText( - anchorPosition.outputLayer[referenceIndex].posX + 10, - anchorPosition.outputLayer[referenceIndex].posY + 12, - anchorPosition.outputLayer[referenceIndex].angleOffset, - 'Output Nodes', - ); + const referenceIndex = 3; // 가운데 위치를 잡기 위한 index값 + const textRenderCodition: boolean = anchorPosition.outputLayer[referenceIndex] != undefined; + if (textRenderCodition) { + // output layer가 화면에 렌더링될때만 텍스트 표기(렌더링 엔진의 특성상 현재 렌더링되고 있는 노드만 array로 들어옴.) + // ex. 현재의 경우 3번째 레이어가 화면에서 벗어나면 화면에 렌더링된 노드는 2개이므로 length가 2. 3번째 index 를 참조할 수 없음. + this.drawText( + anchorPosition.outputLayer[referenceIndex].posX + 10, + anchorPosition.outputLayer[referenceIndex].posY - 90, + anchorPosition.outputLayer[referenceIndex].angleOffset, + 'Input Nodes', + ); + + this.drawText( + anchorPosition.outputLayer[referenceIndex].posX + 10, + anchorPosition.outputLayer[referenceIndex].posY + 12, + anchorPosition.outputLayer[referenceIndex].angleOffset, + 'Output Nodes', + ); + } } renderGrid(gridIndex: number): void { const gridLength: 10 | 8 = gridIndex % 2 === 0 ? 10 : 8; From 80b4bec3e8fb1aae3ddea9183236ac81ff68854f Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:40:38 +0900 Subject: [PATCH 76/94] =?UTF-8?q?feat:=20perceptron=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 04904bb..67fe1b6 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -57,10 +57,23 @@ export class NodeHandler { // TODO: perceptron 렌더링 리팩토링 // TODO: nodeObjectSet 이중 구조 바꾸기 // TODO: 인덱스 참조 문제 디버깅 - // TODO: ScrollEventHandler 싱글톤으로 변경하기 + // TODO: ScrollEventHandler 싱글톤으로 변경하기 -> 보류 // TODO: 터치 엣지 케이스 찾기 } + resetNode(): nodeObjectSet { + const keys = Object.keys(this.nodeObjectSet); + + keys.forEach((key: string) => { + this.nodeObjectSet[key].forEach((_: unknown, i: number) => { + console.log('key:', key, 'i:', i); + this.nodeObjectSet[key][i].setValue(0); + }); + }); + + return this.nodeObjectSet; + } + render(anchorPosition: any): void { Object.keys(anchorPosition).forEach((key: string) => { const layerSize = anchorPosition[key].length; From 0e8ded043a13f5b009cdb5a29ba3c18fd64dea24 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:42:08 +0900 Subject: [PATCH 77/94] =?UTF-8?q?refactor/fix:=20perceptron=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD,?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A1=A4/=ED=84=B0=EC=B9=98=20=EB=B3=91?= =?UTF-8?q?=ED=96=89=20=EB=B6=88=EA=B0=80=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../perceptron/PerceptronController.ts | 36 ++++++++++++------- .../perceptron/ScrollEventHandler.ts | 32 ++++++++--------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index 1bc960a..be07b7e 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -1,7 +1,12 @@ import { IPerceptronControllerProps } from '@/controller/perceptron/types/PerceptronController.ts'; import { NodeHandler } from '@/controller/perceptron/NodeHandler.ts'; import { EdgePositionHandler } from '@/controller/perceptron/EdgeHandler.ts'; -import { DATA_EVENTS, DataEvent, SCROLL_EVENTS } from '@/controller/constants/events.ts'; +import { + DATA_EVENTS, + DataEvent, + DRAWING_EVENTS, + SCROLL_EVENTS, +} from '@/controller/constants/events.ts'; import eventBus from '@/controller/EventBus.ts'; import { eventPayloads } from '@/controller/types/eventBus.ts'; import { compressArr } from '@/controller/perceptron/utils/compressArr.ts'; @@ -53,9 +58,10 @@ class PerceptronController { this.initializeNodeValue(); this.registerScrollEvent(); - this.calculateNodePosition(0, 0); + this.calculateNodePosition(0); this.calculateGridPosition(); this.updatePerceptron(); + this.resetPerceptron(); } initializeNodeValue() { @@ -66,17 +72,17 @@ class PerceptronController { } registerScrollEvent() { - eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (mouseScroll) => { - this.calculateNodePosition(mouseScroll, 0); + eventBus.on(SCROLL_EVENTS.SCROLL_CHANGED, (scroll: number) => { + this.calculateNodePosition(scroll); this.calculateGridPosition(); }); - eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (touchScroll) => { - this.calculateNodePosition(0, touchScroll); + eventBus.on(SCROLL_EVENTS.TOUCH_CHANGED, (scroll: number) => { + this.calculateNodePosition(scroll); this.calculateGridPosition(); }); } - calculateNodePosition(mouseScroll: number, touchScroll: number) { + calculateNodePosition(scroll: number) { this.anchorPosition = { inputLayer: [], hiddenLayer: [], outputLayer: [] }; Object.keys(this.normalizedNetworkInfo).map((key: string, layerIndex: number) => { this.BaseCanvas.saveState(); @@ -90,7 +96,7 @@ class PerceptronController { Array.from({ length: layerSize }, (_: unknown, nodeIndex: number) => nodeIndex).map( (nodeIndex) => { - const scrollOffset = (mouseScroll + touchScroll) / scrollDivider; + const scrollOffset = scroll / scrollDivider; const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; @@ -144,10 +150,16 @@ class PerceptronController { updatePerceptron() { eventBus.on(DATA_EVENTS.NODE_CHANGED, (changedNodeState: nodeState) => { this.nodeObjectSet = this.NodeHandler.updateNode(changedNodeState); - this.calculateNodePosition( - this.ScrollEventHandler.mouseScroll, - this.ScrollEventHandler.touchMove, - ); + this.calculateNodePosition(this.ScrollEventHandler.getScroll()); + this.calculateGridPosition(); + }); + } + + resetPerceptron() { + eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => { + this.nodeObjectSet = this.NodeHandler.resetNode(); + console.log(this.nodeObjectSet); + this.calculateNodePosition(0); this.calculateGridPosition(); }); } diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index ce2436c..4fd0fc3 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -12,8 +12,7 @@ class ScrollEventHandler { private BaseCanvas: IBaseCanvas; private readonly canvasEl: HTMLCanvasElement; - private mouseScroll: number; - private touchMove: number; + private scroll: number; private touchDirection: number; private rotationStack: number; @@ -27,8 +26,7 @@ class ScrollEventHandler { this.BaseCanvas = BaseCanvas; this.canvasEl = BaseCanvas.getCanvas(); // perceptron base canvas - this.mouseScroll = 0; - this.touchMove = 0; + this.scroll = 0; this.touchDirection = 0; this.widestLayer = 0; @@ -53,19 +51,19 @@ class ScrollEventHandler { handleMouseScroll(): void { this.canvasEl.addEventListener('wheel', (e: WheelEvent) => { - this.rightScrollLimit = this.mouseScroll <= this.rightLimit; - this.leftScrollLimit = this.mouseScroll >= this.leftLimit; + this.rightScrollLimit = this.scroll <= this.rightLimit; + this.leftScrollLimit = this.scroll >= this.leftLimit; if (e.deltaY > 0 && this.rightScrollLimit) { this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / (scrollDivider * 2))); - this.mouseScroll += 1; + this.scroll += 1; this.rotationStack += 1; - eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); + eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.scroll); } else if (e.deltaY < 0 && this.leftScrollLimit) { this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / (scrollDivider * 2))); - this.mouseScroll -= 1; + this.scroll -= 1; this.rotationStack -= 1; - eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.mouseScroll); + eventBus.emit(SCROLL_EVENTS.SCROLL_CHANGED, this.scroll); } }); } @@ -80,24 +78,24 @@ class ScrollEventHandler { const moveX = Math.floor(touch.clientX); this.touchDirection = moveX - touchStartX; - this.rightScrollLimit = this.touchMove <= this.rightLimit; - this.leftScrollLimit = this.touchMove >= this.leftLimit; + this.rightScrollLimit = this.scroll <= this.rightLimit; + this.leftScrollLimit = this.scroll >= this.leftLimit; if (this.touchDirection < 0 && this.rightScrollLimit) { this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / (scrollDivider * 2))); - this.touchMove += 1; + this.scroll += 1; this.rotationStack += 1; - eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); + eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.scroll); } else if (this.touchDirection > 0 && this.leftScrollLimit) { this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / (scrollDivider * 2))); - this.touchMove -= 1; + this.scroll -= 1; this.rotationStack -= 1; - eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.touchMove); + eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.scroll); } }); } - getMouseScroll = () => this.mouseScroll; + getScroll = () => this.scroll; } export default ScrollEventHandler; From e99da7bc880e7678fb858911752c51ebd3ac73d6 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:42:47 +0900 Subject: [PATCH 78/94] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/queryPipeline/QueryProcessController.ts | 1 - src/core/NeuralNetworkBase.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts index f72d7cd..ae3a67a 100644 --- a/src/controller/queryPipeline/QueryProcessController.ts +++ b/src/controller/queryPipeline/QueryProcessController.ts @@ -75,7 +75,6 @@ class QueryProcessController { const throttleQuery = throttle((inputs) => { const result: Matrix2D = this.$NN.query(inputs); if (result) { - // eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result); DataStore.setQueryResult(result); } }, this.queryFrequencyMs); diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts index db02b9a..b72084c 100644 --- a/src/core/NeuralNetworkBase.ts +++ b/src/core/NeuralNetworkBase.ts @@ -39,7 +39,6 @@ class NeuralNetworkBase { hiddenOutputs: hiddenOutputs, finalOutputs: finalOutputs, }); - // eventBus.emit(DATA_EVENTS.NODE_CHANGED, { inputs, hiddenOutputs, finalOutputs }); return finalOutputs; } } From 7cb7d254fc24f692472b704b87c239ad2e522789 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:48:44 +0900 Subject: [PATCH 79/94] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 67fe1b6..5f1d200 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -66,7 +66,6 @@ export class NodeHandler { keys.forEach((key: string) => { this.nodeObjectSet[key].forEach((_: unknown, i: number) => { - console.log('key:', key, 'i:', i); this.nodeObjectSet[key][i].setValue(0); }); }); From dd5c2c23de30798e3dc7e319edcac0a58f05d59c Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Tue, 4 Nov 2025 22:50:52 +0900 Subject: [PATCH 80/94] =?UTF-8?q?chore/fix:=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EB=B0=8F=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit perceptron 초기화 시 스크롤 상태가 반영되지 않던 문제 해결 --- src/controller/perceptron/PerceptronController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index be07b7e..deaebba 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -158,8 +158,7 @@ class PerceptronController { resetPerceptron() { eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => { this.nodeObjectSet = this.NodeHandler.resetNode(); - console.log(this.nodeObjectSet); - this.calculateNodePosition(0); + this.calculateNodePosition(this.ScrollEventHandler.getScroll()); this.calculateGridPosition(); }); } From 928668c9234ff440aa10257c2db46a25b316fbcb Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 5 Nov 2025 22:39:15 +0900 Subject: [PATCH 81/94] =?UTF-8?q?feat:=20=EC=B6=94=EB=A1=A0=ED=95=9C=20?= =?UTF-8?q?=EB=8B=B5=20=EC=B6=9C=EB=A0=A5=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 4 ++-- src/controller/AppController.ts | 5 ++--- src/view/DataPresnter.ts | 0 src/view/ViewHandler.ts | 7 ------ src/view/ViewPresenter.ts | 38 +++++++++++++++++++++++++++++++++ src/view/styles/main.css | 14 +++++++++++- 6 files changed, 55 insertions(+), 13 deletions(-) delete mode 100644 src/view/DataPresnter.ts delete mode 100644 src/view/ViewHandler.ts create mode 100644 src/view/ViewPresenter.ts diff --git a/index.html b/index.html index 59159af..e73b62e 100644 --- a/index.html +++ b/index.html @@ -15,11 +15,11 @@

Neural Network

-

visualization v1.0

+

visualization - Mobile Beta v1.0

-
+
diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index 8de4749..f9e8337 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -25,6 +25,7 @@ import ScrollEventHandler from '@/controller/perceptron/ScrollEventHandler.ts'; import EdgeRenderer from '@/view/components/perceptron/EdgeRenderer.ts'; import GridRenderer from '@/view/components/perceptron/GridRenderer.ts'; import GridHandler from '@/controller/perceptron/GridHandler.ts'; +import ViewPresenter from '@/view/ViewPresenter.ts'; class AppController { private $WM: IWeightManager; @@ -65,9 +66,7 @@ class AppController { BaseCanvas: perceptronBaseCanvas, ScrollEventHandler: new ScrollEventHandler(perceptronBaseCanvas), }); - eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => { - console.log('RESULT CHANGED', data); - }); + new ViewPresenter(); } } diff --git a/src/view/DataPresnter.ts b/src/view/DataPresnter.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/view/ViewHandler.ts b/src/view/ViewHandler.ts deleted file mode 100644 index e2ded8b..0000000 --- a/src/view/ViewHandler.ts +++ /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/styles/main.css b/src/view/styles/main.css index ef53a0c..241541b 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -39,12 +39,24 @@ main { } #query-result { - display: block; position: relative; width: 100%; height: 35%; /*background: #757575;*/ + display: flex; + justify-content: center; + justify-items: flex-end; + z-index: 5; + div { + display: block; + height: 100%; + line-height: 2; + font-size: 12rem; + font-weight: 700; + font-family: "Noto Sans Syriac Western", sans-serif; + color: #171717; + } } #canvas-unit { From 2459204ce1f20c84130c7511b7899a2f43b4a521 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 8 Nov 2025 17:23:55 +0900 Subject: [PATCH 82/94] =?UTF-8?q?feat/design:=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=95=20=EC=A7=80=EC=9B=90=EC=9D=84=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?UI=20Adapter=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/AppController.ts | 2 + src/controller/DataStore.ts | 15 ++++- src/controller/constants/rendererConfig.ts | 1 + .../constants/types/rendererConfig.ts | 1 + src/controller/perceptron/EdgeHandler.ts | 5 +- .../perceptron/PerceptronController.ts | 12 +++- .../components/perceptron/EdgeRenderer.ts | 5 ++ src/view/styles/ViewportAdapter.ts | 60 +++++++++++++++++++ src/view/styles/main.css | 13 ++-- 9 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 src/view/styles/ViewportAdapter.ts diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts index f9e8337..038b08f 100644 --- a/src/controller/AppController.ts +++ b/src/controller/AppController.ts @@ -26,6 +26,7 @@ import EdgeRenderer from '@/view/components/perceptron/EdgeRenderer.ts'; import GridRenderer from '@/view/components/perceptron/GridRenderer.ts'; import GridHandler from '@/controller/perceptron/GridHandler.ts'; import ViewPresenter from '@/view/ViewPresenter.ts'; +import ViewportAdapter from '@/view/styles/ViewportAdapter.ts'; class AppController { private $WM: IWeightManager; @@ -45,6 +46,7 @@ class AppController { $NN: $NN, }); + new ViewportAdapter(); const perceptronBaseCanvas = new BaseCanvas(); const nodeRenderer = new NodeRenderer(perceptronBaseCanvas.getCtx()); const nodeHandler = new NodeHandler({ diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts index b79635e..2d8d4d7 100644 --- a/src/controller/DataStore.ts +++ b/src/controller/DataStore.ts @@ -1,5 +1,7 @@ import eventBus from './EventBus.js'; import { DATA_EVENTS } from './constants/events.js'; +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { displayNodes, displayEdges } = RENDERER_CONFIG; import { nodeState } from './types/DataStore'; import { Matrix2D } from '@/core/ops/types/OpsType.ts'; @@ -10,10 +12,14 @@ class DataStore { private nodeState: nodeState | null = null; private queryResult: Matrix2D | null = null; + private PERCEPTRON_CONFIG: { displayNodes: number; displayEdges: number } = null; + constructor() { this.queryInfo = null; this.nodeState = null; this.queryResult = null; + + this.PERCEPTRON_CONFIG = { displayNodes: displayNodes, displayEdges: displayEdges }; } setQueryInfo(queryInfo: number[]): void { @@ -47,7 +53,14 @@ class DataStore { return this.queryResult; } - reset() { + setPerceptronConfig(perceptronConfig: { displayNodes: number; displayEdges: number }): void { + this.PERCEPTRON_CONFIG = perceptronConfig; + } + getPerceptronConfig(): { displayNodes: number; displayEdges: number } { + return this.PERCEPTRON_CONFIG; + } + + resetPerceptronState(): void { this.queryInfo = null; this.nodeState = null; this.queryResult = null; diff --git a/src/controller/constants/rendererConfig.ts b/src/controller/constants/rendererConfig.ts index 33a91fe..2b340cb 100644 --- a/src/controller/constants/rendererConfig.ts +++ b/src/controller/constants/rendererConfig.ts @@ -5,6 +5,7 @@ export const RENDERER_CONFIG: rendererConfig = { degree: Math.PI / 180, // 1도(radian -> degree) mouseScroll: 1, // 마우스 스크롤 값 displayNodes: 29, // 화면에 표시할 노드의 개수 + displayEdges: 15, // 각 노드당 연결될 간선의 개수 scrollDivider: 4, // 스크롤 n틱당 노드 한개 표시(ex. 스크롤 4번시 캔버스 회전) gridWidth: 1, // 그리드간 간격 layerHeight: [600, 635, 670], // 노드의 레이어당 높이(distance from center) diff --git a/src/controller/constants/types/rendererConfig.ts b/src/controller/constants/types/rendererConfig.ts index 054bfa0..070610c 100644 --- a/src/controller/constants/types/rendererConfig.ts +++ b/src/controller/constants/types/rendererConfig.ts @@ -3,6 +3,7 @@ export interface rendererConfig { degree: number; mouseScroll: number; displayNodes: number; + displayEdges: number; scrollDivider: number; gridWidth: number; layerHeight: number[]; diff --git a/src/controller/perceptron/EdgeHandler.ts b/src/controller/perceptron/EdgeHandler.ts index a44feba..b866f76 100644 --- a/src/controller/perceptron/EdgeHandler.ts +++ b/src/controller/perceptron/EdgeHandler.ts @@ -1,3 +1,6 @@ +import { RENDERER_CONFIG } from '@/controller/constants/rendererConfig.ts'; +const { displayEdges } = RENDERER_CONFIG; + class EdgeHandler { private EdgeRenderer: IEdgeRenderer; private BaseCanvas: IBaseCanvas; @@ -30,7 +33,7 @@ class EdgeHandler { return current .flatMap((a, aIndex) => { const ratio = current.length <= 1 ? 0 : aIndex / (current.length - 1); - const subset = subsetByRatio(next, ratio, 13); + const subset = subsetByRatio(next, ratio, displayEdges); return subset.map((b) => [a, b]); }) diff --git a/src/controller/perceptron/PerceptronController.ts b/src/controller/perceptron/PerceptronController.ts index deaebba..1b95b93 100644 --- a/src/controller/perceptron/PerceptronController.ts +++ b/src/controller/perceptron/PerceptronController.ts @@ -97,8 +97,16 @@ class PerceptronController { Array.from({ length: layerSize }, (_: unknown, nodeIndex: number) => nodeIndex).map( (nodeIndex) => { const scrollOffset = scroll / scrollDivider; - const displayStart = layerSize / 2 - displayNodes / 2 + scrollOffset; - const displayEnd = layerSize / 2 + displayNodes / 2 + scrollOffset; + const displayStart = + layerSize / 2 - + DataStore.getPerceptronConfig().displayNodes / 2 + + scrollOffset; + const displayEnd = + layerSize / 2 + + DataStore.getPerceptronConfig().displayNodes / 2 + + scrollOffset; + + console.log(); const displayCondition = nodeIndex >= displayStart && nodeIndex < displayEnd; diff --git a/src/view/components/perceptron/EdgeRenderer.ts b/src/view/components/perceptron/EdgeRenderer.ts index e121e72..52e3052 100644 --- a/src/view/components/perceptron/EdgeRenderer.ts +++ b/src/view/components/perceptron/EdgeRenderer.ts @@ -6,6 +6,11 @@ class EdgeRenderer { } 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; diff --git a/src/view/styles/ViewportAdapter.ts b/src/view/styles/ViewportAdapter.ts new file mode 100644 index 0000000..358027b --- /dev/null +++ b/src/view/styles/ViewportAdapter.ts @@ -0,0 +1,60 @@ +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('--indicator-margin', `-15px`); + 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 241541b..572c534 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -81,7 +81,8 @@ main { height: 110px; position: fixed; - bottom: 550px; + bottom: var(--indicator-height); + /*bottom: 550px;*/ display: block; z-index: 15; @@ -90,15 +91,17 @@ main { } #left-indicator{ - left: -15px; - transform: rotate(-10deg); + /*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: -15px; - transform: rotate(10deg); + 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%); From 06cf863ca3986ace1e9d8d781243910e6f6b442c Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 8 Nov 2025 17:24:07 +0900 Subject: [PATCH 83/94] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/GridHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controller/perceptron/GridHandler.ts b/src/controller/perceptron/GridHandler.ts index 68e082a..b8690f6 100644 --- a/src/controller/perceptron/GridHandler.ts +++ b/src/controller/perceptron/GridHandler.ts @@ -51,7 +51,6 @@ class GridHandler { this.BaseCanvas.rotateCanvas(true, ((angleOffset * Math.PI) / 180) * 1.01); this.BaseCanvas.rotateCanvas(true, degree * 90); this.BaseCanvas.getCtx().fillStyle = 'rgb(133,133,133)'; - // this.BaseCanvas.getCtx().fillText(`${text}`, x + 10, y); this.BaseCanvas.getCtx().fillText(`${text}`, 0, 0); this.BaseCanvas.getCtx().fillStyle = 'rgb(255, 255, 255)'; this.BaseCanvas.restoreState(); From cea3e690c5f5c69cf120b60082a0f65e8236bf33 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sat, 8 Nov 2025 23:19:59 +0900 Subject: [PATCH 84/94] =?UTF-8?q?chore:=20TODO=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/NodeHandler.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/controller/perceptron/NodeHandler.ts b/src/controller/perceptron/NodeHandler.ts index 5f1d200..94e8988 100644 --- a/src/controller/perceptron/NodeHandler.ts +++ b/src/controller/perceptron/NodeHandler.ts @@ -54,11 +54,7 @@ export class NodeHandler { }); }); return this.nodeObjectSet; - // TODO: perceptron 렌더링 리팩토링 - // TODO: nodeObjectSet 이중 구조 바꾸기 - // TODO: 인덱스 참조 문제 디버깅 - // TODO: ScrollEventHandler 싱글톤으로 변경하기 -> 보류 - // TODO: 터치 엣지 케이스 찾기 + // TODO: ScrollEventHandler 싱글톤으로 변경하기 -> 보류(결정 문서 참고) } resetNode(): nodeObjectSet { From ce1f5adb6088fa0f1ff9b0e97e310e34e372a03a Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 00:24:29 +0900 Subject: [PATCH 85/94] test: deploy action --- .github/workflows/deploy.yml | 38 +++ .idea/inspectionProfiles/Project_Default.xml | 16 + package.json | 2 + pnpm-lock.yaml | 337 +++++++++++++++++++ vite.config.ts | 9 + 5 files changed, 402 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a657cca --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Deploy perceptron preview + +on: + push: + branches: + - feat/perceptron + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v3 + with: + version: 9 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install deps + run: pnpm install --frozen-lockfile + + - name: Build + env: GITHUB_REF_NAME: ${{ github.ref_name }} + run: pnpm build + + - name: Deploy + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./dist + publish_branch: gh-pages + destination_dir: feat/perceptron \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..bd46cef --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/package.json b/package.json index 2bacd31..842ffab 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "dev": "vite", "build": "tsc && vite build", + "deploy": "gh-pages -d dist", "preview": "vite preview", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -19,6 +20,7 @@ "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.3", + "gh-pages": "^6.3.0", "prettier": "3.6.2", "vite": "^7.0.6" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6b35da..7e9ab80 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: eslint-plugin-prettier: specifier: ^5.5.3 version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2) + gh-pages: + specifier: ^6.3.0 + version: 6.3.0 prettier: specifier: 3.6.2 version: 3.6.2 @@ -243,6 +246,18 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -376,12 +391,23 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -397,6 +423,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -416,11 +449,22 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + email-addresses@5.0.0: + resolution: {integrity: sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==} + esbuild@0.25.8: resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} hasBin: true + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -493,12 +537,19 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -511,6 +562,26 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filename-reserved-regex@2.0.0: + resolution: {integrity: sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==} + engines: {node: '>=4'} + + filenamify@4.3.0: + resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==} + engines: {node: '>=8'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -522,11 +593,24 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gh-pages@6.3.0: + resolution: {integrity: sha512-Ot5lU6jK0Eb+sszG8pciXdjMXdBJ5wODvgjR+imihTqsUWF2K6dJ9HST55lgqcs8wWcw6o6wAsUzfcYRhJPXbA==} + engines: {node: '>=10'} + hasBin: true + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -535,6 +619,13 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -559,6 +650,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -575,6 +670,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -582,6 +680,10 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -589,6 +691,18 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -607,14 +721,26 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -627,13 +753,25 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -655,15 +793,29 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup@4.46.2: resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -672,6 +824,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -680,6 +836,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-outer@1.0.1: + resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==} + engines: {node: '>=0.10.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -692,6 +852,14 @@ packages: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-repeated@1.0.0: + resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} + engines: {node: '>=0.10.0'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -699,6 +867,10 @@ packages: undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -892,6 +1064,18 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + '@pkgr/core@0.2.9': {} '@rollup/rollup-android-arm-eabi@4.46.2': @@ -981,6 +1165,10 @@ snapshots: argparse@2.0.1: {} + array-union@2.1.0: {} + + async@3.2.6: {} + balanced-match@1.0.2: {} brace-expansion@1.1.12: @@ -988,6 +1176,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + callsites@3.1.0: {} chalk@4.1.2: @@ -1001,6 +1193,10 @@ snapshots: color-name@1.1.4: {} + commander@13.1.0: {} + + commondir@1.0.1: {} + concat-map@0.0.1: {} cross-spawn@7.0.6: @@ -1015,6 +1211,12 @@ snapshots: deep-is@0.1.4: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + email-addresses@5.0.0: {} + esbuild@0.25.8: optionalDependencies: '@esbuild/aix-ppc64': 0.25.8 @@ -1044,6 +1246,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.8 '@esbuild/win32-x64': 0.25.8 + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@9.32.0): @@ -1130,10 +1334,22 @@ snapshots: fast-diff@1.3.0: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -1142,6 +1358,29 @@ snapshots: dependencies: flat-cache: 4.0.1 + filename-reserved-regex@2.0.0: {} + + filenamify@4.3.0: + dependencies: + filename-reserved-regex: 2.0.0 + strip-outer: 1.0.1 + trim-repeated: 1.0.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-cache-dir@3.3.2: + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1154,15 +1393,46 @@ snapshots: flatted@3.3.3: {} + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + fsevents@2.3.3: optional: true + gh-pages@6.3.0: + dependencies: + async: 3.2.6 + commander: 13.1.0 + email-addresses: 5.0.0 + filenamify: 4.3.0 + find-cache-dir: 3.3.2 + fs-extra: 11.3.2 + globby: 11.1.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@14.0.0: {} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + has-flag@4.0.0: {} ignore@5.3.2: {} @@ -1180,6 +1450,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-number@7.0.0: {} + isexe@2.0.0: {} js-yaml@4.1.0: @@ -1192,6 +1464,12 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -1201,12 +1479,27 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -1226,14 +1519,24 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@2.2.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -1242,10 +1545,18 @@ snapshots: path-key@3.1.1: {} + path-type@4.0.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.3: {} + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -1262,8 +1573,12 @@ snapshots: punycode@2.3.1: {} + queue-microtask@1.2.3: {} + resolve-from@4.0.0: {} + reusify@1.1.0: {} + rollup@4.46.2: dependencies: '@types/estree': 1.0.8 @@ -1290,16 +1605,28 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.46.2 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + semver@6.3.1: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + slash@3.0.0: {} + source-map-js@1.2.1: {} strip-json-comments@3.1.1: {} + strip-outer@1.0.1: + dependencies: + escape-string-regexp: 1.0.5 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -1313,12 +1640,22 @@ snapshots: fdir: 6.4.6(picomatch@4.0.3) picomatch: 4.0.3 + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-repeated@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 undici-types@7.10.0: {} + universalify@2.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 diff --git a/vite.config.ts b/vite.config.ts index e53a7ea..bc2435b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,16 @@ 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 = `/${repo}/${branch}/`; +} + export default defineConfig({ + base: base, resolve: { alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }], }, From 21308cf7b0008511d3f0c94ea78d6308dac92d34 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 00:30:12 +0900 Subject: [PATCH 86/94] fix: yaml env syntax --- .github/workflows/deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a657cca..61f6660 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,7 +26,8 @@ jobs: run: pnpm install --frozen-lockfile - name: Build - env: GITHUB_REF_NAME: ${{ github.ref_name }} + env: + GITHUB_REF_NAME: ${{ github.ref_name }} run: pnpm build - name: Deploy From a29fa37cf064ef0f6a7be0daf72cae4d82d1060f Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 00:34:16 +0900 Subject: [PATCH 87/94] fix: yaml env syntax --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 842ffab..7e7c6a2 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "deploy": "gh-pages -d dist", "preview": "vite preview", "lint": "eslint .", From 04495d34853b13eb9278272bd6cfdf7b1db1155a Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 00:46:42 +0900 Subject: [PATCH 88/94] fix: vite config base --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index bc2435b..02b184e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,7 @@ const branch = process.env.GITHUB_REF_NAME || ''; let base = `/${repo}/`; if (branch.startsWith('feat/')) { - base = `/${repo}/${branch}/`; + base = './'; } export default defineConfig({ From 12a5dc54e557912b4d370ba2bb9ef6f7fafce0ba Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 19:52:37 +0900 Subject: [PATCH 89/94] test: mobile viewport error test --- src/view/components/perceptron/BaseCanvas.ts | 3 +-- src/view/styles/main.css | 16 ++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/view/components/perceptron/BaseCanvas.ts b/src/view/components/perceptron/BaseCanvas.ts index c41bd71..bd67631 100644 --- a/src/view/components/perceptron/BaseCanvas.ts +++ b/src/view/components/perceptron/BaseCanvas.ts @@ -23,8 +23,7 @@ class BaseCanvas { setupCanvas() { this.canvas.width = 2000; - // this.canvas.height = window.innerHeight * (5 / 20); - this.canvas.height = 1000; + this.canvas.height = 720; this.ctx.fillStyle = 'rgb(255,255,255)'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 572c534..88efcb2 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -43,15 +43,18 @@ main { width: 100%; height: 35%; /*background: #757575;*/ + display: flex; justify-content: center; - justify-items: flex-end; - + /*justify-items: flex-end;*/ + align-items: center; z-index: 5; div { display: block; - height: 100%; - line-height: 2; + + width: fit-content; + height: fit-content; + font-size: 12rem; font-weight: 700; font-family: "Noto Sans Syriac Western", sans-serif; @@ -63,7 +66,8 @@ main { position: relative; display: block; width: 100%; - height: 65%; + height: 720px; + /*height: 720px;*/ z-index: 2; #position-aligner { @@ -71,7 +75,7 @@ main { width: 100%; height: 100%; - top: 85%; + top: 50%; left: 50%; transform: translate(-50%, -50%); } From 93b4852068fcdcf3cc04b8af22179c892c3d0678 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 20:11:25 +0900 Subject: [PATCH 90/94] fix: safari browser canvas clip-path rendering issue --- src/view/styles/main.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 88efcb2..f34f088 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -55,7 +55,7 @@ main { width: fit-content; height: fit-content; - font-size: 12rem; + font-size: 11rem; font-weight: 700; font-family: "Noto Sans Syriac Western", sans-serif; color: #171717; @@ -164,10 +164,12 @@ main { #user-input-canvas { display: block; - z-index: 2; - width: 100%; height: 100%; + + z-index: 2; + transform: translateZ(0); + clip-path: url(#arc-clip); } From 9d2435534de0162adde0e6779bd9a3d39b00a15f Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 20:16:18 +0900 Subject: [PATCH 91/94] fix: safari browser canvas clip-path rendering issue 2 --- src/view/styles/main.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/view/styles/main.css b/src/view/styles/main.css index f34f088..001f37e 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -168,7 +168,10 @@ main { height: 100%; z-index: 2; + /* safari client 대응 */ transform: translateZ(0); + will-change: transform; + overflow: hidden; clip-path: url(#arc-clip); } From 6c23021a0bd4cfbe49d966632ec60e174761eb72 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Sun, 9 Nov 2025 20:22:37 +0900 Subject: [PATCH 92/94] fix: safari browser canvas clip-path rendering issue 3 --- src/view/styles/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/view/styles/main.css b/src/view/styles/main.css index 001f37e..b7ec50b 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -174,6 +174,7 @@ main { overflow: hidden; clip-path: url(#arc-clip); + -webkit-clip-path: url(#arc-clip); } From adbe68c843bd0c3a075adf8d2eaf9eb76d730344 Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Wed, 12 Nov 2025 22:37:32 +0900 Subject: [PATCH 93/94] =?UTF-8?q?feat:=20=EB=85=B8=EB=93=9C=20swipe?= =?UTF-8?q?=EC=8B=9C=20Y=EC=B6=95=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/perceptron/ScrollEventHandler.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/controller/perceptron/ScrollEventHandler.ts b/src/controller/perceptron/ScrollEventHandler.ts index 4fd0fc3..f697cf2 100644 --- a/src/controller/perceptron/ScrollEventHandler.ts +++ b/src/controller/perceptron/ScrollEventHandler.ts @@ -13,7 +13,8 @@ class ScrollEventHandler { private readonly canvasEl: HTMLCanvasElement; private scroll: number; - private touchDirection: number; + private touchDirectionX: number; + private touchDirectionY: number; private rotationStack: number; private widestLayer: number; @@ -68,25 +69,31 @@ class ScrollEventHandler { }); } handleTouchMove(): void { - let touchStartX = 0; + let touchStartX: number = 0; + let touchStartY: number = 0; this.canvasEl.addEventListener('touchstart', (e: TouchEvent) => { const touch = e.touches[0]; touchStartX = Math.floor(touch.clientX); + touchStartY = Math.floor(touch.clientY); }); this.canvasEl.addEventListener('touchmove', (e: TouchEvent) => { const touch = e.touches[0]; const moveX = Math.floor(touch.clientX); - this.touchDirection = moveX - touchStartX; + const moveY = Math.floor(touch.clientY); + this.touchDirectionX = moveX - touchStartX; + this.touchDirectionY = moveY - touchStartY; + + if (Math.abs(this.touchDirectionX) < Math.abs(this.touchDirectionY)) return; this.rightScrollLimit = this.scroll <= this.rightLimit; this.leftScrollLimit = this.scroll >= this.leftLimit; - if (this.touchDirection < 0 && this.rightScrollLimit) { + if (this.touchDirectionX < 0 && this.rightScrollLimit) { this.BaseCanvas.rotateCanvas(false, degree * (rotationDelta / (scrollDivider * 2))); this.scroll += 1; this.rotationStack += 1; eventBus.emit(SCROLL_EVENTS.TOUCH_CHANGED, this.scroll); - } else if (this.touchDirection > 0 && this.leftScrollLimit) { + } else if (this.touchDirectionX > 0 && this.leftScrollLimit) { this.BaseCanvas.rotateCanvas(true, degree * (rotationDelta / (scrollDivider * 2))); this.scroll -= 1; this.rotationStack -= 1; From 8b84476d156cdfbeaa4a73e779038b3939f19afb Mon Sep 17 00:00:00 2001 From: Sunwoo Ha Date: Fri, 14 Nov 2025 22:28:21 +0900 Subject: [PATCH 94/94] feat: Partial responsive support --- src/view/styles/ViewportAdapter.ts | 8 ++++++++ src/view/styles/main.css | 21 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/view/styles/ViewportAdapter.ts b/src/view/styles/ViewportAdapter.ts index 358027b..e127623 100644 --- a/src/view/styles/ViewportAdapter.ts +++ b/src/view/styles/ViewportAdapter.ts @@ -21,7 +21,15 @@ class ViewportAdapter { 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({ diff --git a/src/view/styles/main.css b/src/view/styles/main.css index b7ec50b..4bdf37c 100644 --- a/src/view/styles/main.css +++ b/src/view/styles/main.css @@ -184,9 +184,9 @@ svg{ #clear { position: absolute; - z-index: 10; + z-index: 20; - bottom: 480px; + top: 480px; right: 10px; width: 30px; @@ -206,6 +206,23 @@ svg{ } } +@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);