├── .env ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── algorithms │ ├── graph-algorithms │ │ ├── a_star.ts │ │ ├── best-first-search.ts │ │ ├── bfs.ts │ │ ├── dijkstra.ts │ │ └── graph.ts │ └── sorting-algorithms │ │ ├── bubble-sort.ts │ │ ├── heap-sort.ts │ │ ├── merge-sort.ts │ │ ├── quick-sort.ts │ │ └── sorting.ts ├── assets │ ├── icons │ │ ├── linkedinBlue.png │ │ └── linkedinWhite.png │ └── images │ │ ├── destination.png │ │ ├── homepage │ │ └── graph.png │ │ ├── logo │ │ └── logo.png │ │ ├── simple.png │ │ ├── start.png │ │ ├── wall.png │ │ └── weight.png ├── components │ ├── AlgPropSelector │ │ ├── AlgPropSelector.module.css │ │ └── index.tsx │ ├── GithubStarCount │ │ ├── GithubStarCount.module.css │ │ └── index.tsx │ ├── Graph │ │ ├── Graph.module.css │ │ ├── Node │ │ │ ├── Node.module.css │ │ │ └── index.tsx │ │ └── index.tsx │ ├── GraphOptionsButtonGroup │ │ ├── GraphOptionsButtonGrooup.module.css │ │ └── index.tsx │ ├── LabeledSlider │ │ ├── LabeledSlider.module.css │ │ └── index.tsx │ ├── Navigation │ │ └── Toolbar │ │ │ ├── Toolbar.module.css │ │ │ └── index.tsx │ ├── SortOptionsButtonGroup │ │ ├── SortOptionsButtonGroup.module.css │ │ └── index.tsx │ └── SortingStacks │ │ ├── SortingStack │ │ ├── SortingStack.module.css │ │ └── index.tsx │ │ ├── SortingStacks.module.css │ │ └── index.tsx ├── containers │ ├── AlgVisualizerRouting │ │ └── index.tsx │ ├── GraphContainerAlgorithms │ │ ├── GraphContainerAlgorithms.module.css │ │ └── index.tsx │ ├── HomePage │ │ ├── AlgorithmsSection │ │ │ ├── AlgorithmClassCard │ │ │ │ ├── AlgorithmClassCard.module.css │ │ │ │ └── index.tsx │ │ │ ├── AlgorithmsSection.module.css │ │ │ └── index.tsx │ │ ├── PresentationSection │ │ │ ├── PresentationSection.module.css │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── overrides.css │ └── SortingContainerAlgorithms │ │ ├── SortingContainerAlgorithms.module.css │ │ └── index.tsx ├── hoc │ └── Layout │ │ └── index.tsx ├── hooks │ └── hooks.ts ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── react-fullpage.d.ts ├── serviceWorker.ts ├── setupTests.ts ├── store │ ├── app │ │ ├── actions.ts │ │ ├── reducer.ts │ │ ├── state.ts │ │ └── types.ts │ ├── graph │ │ ├── actions.ts │ │ ├── reducer.ts │ │ ├── state.ts │ │ └── types.ts │ ├── index.ts │ ├── sorting │ │ ├── actions.ts │ │ ├── reducer.ts │ │ ├── state.ts │ │ └── types.ts │ └── state.ts ├── tests │ └── unit │ │ ├── algorithms │ │ └── graph-algorithms │ │ │ └── graph.test.ts │ │ └── store │ │ ├── app │ │ ├── actions.test.ts │ │ └── reducer.test.ts │ │ └── graph │ │ ├── actions.test.ts │ │ └── reducer.test.ts └── utils │ ├── app-utils-functions.ts │ ├── graph-utils-functions.ts │ ├── sorting-utils-functions.ts │ └── types │ ├── app-types │ ├── alg-speed-type.ts │ ├── algorithm-classes-types.ts │ ├── consts.ts │ ├── dictionary.ts │ └── window.ts │ ├── graph-types │ ├── consts.ts │ ├── graph-algorithm-types.ts │ ├── graph-results-types.ts │ ├── navigation-item-type.ts │ ├── node-type-button-type.ts │ ├── node-type.ts │ └── table-node-type.ts │ └── sorting-types │ ├── array-stack-type.ts │ ├── consts.ts │ ├── sorting-alorithm-types.ts │ ├── sorting-default-values.ts │ ├── sorting-results-types.ts │ └── sorting-stack-type.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | src/serviceWorker.ts 4 | jest.config.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "prettier"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "prettier" 10 | ], 11 | "rules": { 12 | "no-console": 1, // Means warning 13 | "prettier/prettier": 2, // Means error 14 | "no-use-before-define": "off", 15 | "@typescript-eslint/no-use-before-define": ["error"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .vscode 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithm Visualizer 2 | 3 | Application for visualizing different types of algorithms. If you want to play with it, you cand find it [here](https://georgianbadita.github.io/algorithm-visualizer). 4 | 5 | - [Algorithm Visualizer](#algorithm-visualizer) 6 | - [General info](#general-info) 7 | - [Technologies](#technologies) 8 | - [Setup](#setup) 9 | - [Demo](#demo) 10 | - [Future work](#future-work) 11 | 12 | ## General info 13 | 14 | The aim of this project is to help new students starting competitive programming or data structures and algorithms courses at university level. 15 | The application supports the following algorithms: 16 | 17 | - **Graphs**: 18 | - Dijkstra's Algorithm 19 | - Breadth First Search 20 | - A \* Pathfinding Algorithm 21 | - Best First Search 22 | - **Sorting**: 23 | - Bubble Sort 24 | - Merge Sort 25 | - Quick Sort 26 | - Heap Sort 27 | 28 | ## Technologies 29 | 30 | The main technologies used for developing this project are: 31 | 32 | - Javascript 33 | - ReactJS 34 | - Redux 35 | - ReactFullPage for the home page 36 | 37 | ## Setup 38 | 39 | In order to setup the project locally, use the following commands: 40 | 41 | ```console 42 | git clone https://github.com/GeorgianBadita/algorithm-visualizer.git 43 | cd algorithm-visualizer 44 | npm install 45 | npm run start 46 | ``` 47 | 48 | ## Demo 49 | 50 | **Graphs** 51 | 52 | ![Algorithm-Visualizer Demo](https://media.giphy.com/media/mhcti1UA16oZI0vW6g/source.gif) 53 | 54 | **Sorting** 55 | 56 | ![Algorithm-Visualizer Demo](https://media.giphy.com/media/OrvgRvM18ooYQNbOJ0/source.gif) 57 | 58 | ## Future work 59 | 60 | In the future I aim to include more classes of algorithms and also polish the work that has been done so far. 61 | I plan to include: 62 | 63 | - **Dynamic Programming Algorithms** 64 | - **Divide and Conquer** 65 | - **Backtracking** 66 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "/src" 4 | ], 5 | "testMatch": [ 6 | "**/__tests__/**/*.+(ts|tsx|js)", 7 | "**/?(*.)+(spec|test).+(ts|tsx|js)" 8 | ], 9 | "transform": { 10 | "^.+\\.(ts|tsx)$": "ts-jest", 11 | ".+\\.(css|styl|less|sass|scss)$": "jest-transform-css" 12 | }, 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alg-visualizer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@fullpage/react-fullpage": "^0.1.18", 7 | "@reduxjs/toolkit": "^1.4.0", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.3.2", 10 | "@testing-library/user-event": "^7.1.2", 11 | "@types/node": "^12.0.0", 12 | "@types/react": "^16.9.0", 13 | "@types/react-dom": "^16.9.0", 14 | "@types/react-redux": "^7.1.11", 15 | "@types/react-router-bootstrap": "^0.24.5", 16 | "@types/react-router-dom": "^5.1.6", 17 | "@types/redux-logger": "^3.0.8", 18 | "bootstrap": "^4.5.3", 19 | "create-react-app": "^4.0.1", 20 | "evergreen-ui": "^5.1.2", 21 | "identity-obj-proxy": "^3.0.0", 22 | "jest-transform-css": "^2.1.0", 23 | "npm": "^6.14.10", 24 | "rc-slider": "^9.7.1", 25 | "react": "^16.13.1", 26 | "react-animated-css": "^1.2.1", 27 | "react-animated-slider": "^2.0.0", 28 | "react-awesome-slider": "^4.1.0", 29 | "react-bootstrap": "^1.4.0", 30 | "react-bootstrap-range-slider": "^2.0.2", 31 | "react-dom": "^16.13.1", 32 | "react-dropdown": "^1.9.0", 33 | "react-redux": "^7.2.2", 34 | "react-router-bootstrap": "^0.25.0", 35 | "react-router-dom": "^5.2.0", 36 | "react-scripts": "3.4.3", 37 | "react-toastify": "^6.1.0", 38 | "redux": "^4.0.5", 39 | "redux-logger": "^3.0.6", 40 | "semantic-ui-css": "^2.4.1", 41 | "semantic-ui-react": "^2.0.1", 42 | "ts-heap": "^1.1.3" 43 | }, 44 | "scripts": { 45 | "start": "react-scripts start", 46 | "build": "react-scripts build", 47 | "test": "jest", 48 | "eject": "react-scripts eject", 49 | "lint": "eslint . --ext .ts ", 50 | "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write", 51 | "predeploy": "npm run build", 52 | "deploy": "gh-pages -d build" 53 | }, 54 | "eslintConfig": { 55 | "extends": "react-app" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | }, 69 | "devDependencies": { 70 | "@types/jest": "^24.9.1", 71 | "@types/react-bootstrap-range-slider": "^1.2.0", 72 | "@typescript-eslint/eslint-plugin": "^4.2.0", 73 | "@typescript-eslint/parser": "^4.3.0", 74 | "eslint": "^6.8.0", 75 | "eslint-config-airbnb-typescript": "11.0.0", 76 | "eslint-config-prettier": "^6.12.0", 77 | "eslint-plugin-eslint-comments": "^3.2.0", 78 | "eslint-plugin-import": "^2.22.1", 79 | "eslint-plugin-jsx-a11y": "^6.3.1", 80 | "eslint-plugin-prettier": "^3.1.4", 81 | "eslint-plugin-react": "^7.21.3", 82 | "eslint-plugin-react-hooks": "^4.1.2", 83 | "gh-pages": "^3.1.0", 84 | "jest": "^26.6.3", 85 | "prettier": "2.2.1", 86 | "ts-jest": "^26.4.4", 87 | "typescript": "^3.7.5" 88 | }, 89 | "jest": { 90 | "moduleNameMapper": { 91 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", 92 | "\\.(css|less)$": "identity-obj-proxy" 93 | } 94 | }, 95 | "homepage": "http://GeorgianBadita.github.io/algorithm-visualizer" 96 | } 97 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 28 | Algorithm Visualizer 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from './hoc/Layout'; 3 | import AlgVisualizerRouting from './containers/AlgVisualizerRouting'; 4 | import { Provider } from 'react-redux'; 5 | import { ToastContainer } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | import 'semantic-ui-css/semantic.min.css'; 8 | 9 | type AppProps = { 10 | store: any; 11 | }; 12 | 13 | const App = (props: AppProps): JSX.Element => { 14 | return ( 15 | 16 |
17 | 18 | 19 | 20 | 31 |
32 |
33 | ); 34 | }; 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /src/algorithms/graph-algorithms/a_star.ts: -------------------------------------------------------------------------------- 1 | import { GraphAlgOutput, Pair } from '../../utils/types/graph-types/graph-results-types'; 2 | import { Edges, Graph, GraphNode, ParentVectorType } from './graph'; 3 | import { Heap } from 'ts-heap'; 4 | import { MyDictionary } from '../../utils/types/app-types/dictionary'; 5 | import { GraphState } from '../../store/graph/state'; 6 | import { computeDistance, fromIndexToPair } from '../../utils/graph-utils-functions'; 7 | 8 | export const aStar = (source: GraphNode, destination: GraphNode, graphState: GraphState): GraphAlgOutput => { 9 | const heapComparator = (node1: GraphNode, node2: GraphNode): number => { 10 | if (node1.weight && node2.weight) { 11 | const p1: Pair = fromIndexToPair(parseInt(node1.id, 10), graphState.width); 12 | const p2: Pair = fromIndexToPair(parseInt(node2.id, 10), graphState.width); 13 | const dest: Pair = fromIndexToPair(parseInt(destination.id, 10), graphState.width); 14 | 15 | return node1.weight + computeDistance(p1, dest) - node2.weight - computeDistance(p2, dest); 16 | } 17 | return 0; 18 | }; 19 | 20 | const pq: Heap = new Heap(heapComparator); 21 | const graph: Graph = { 22 | numberOfNodes: graphState.numberOfNodes, 23 | nodes: graphState.nodes, 24 | edges: graphState.edges, 25 | }; 26 | graph.nodes = graph.nodes.map((elem: GraphNode) => { 27 | if (elem.weight) { 28 | return elem; 29 | } 30 | return { id: elem.id, weight: 1 }; 31 | }); 32 | graph.edges = Object.keys(graph.edges).reduce((oldE: Edges, key: string) => { 33 | oldE[key] = graph.edges[key].map((elem: GraphNode) => { 34 | if (elem.weight) { 35 | return elem; 36 | } 37 | return { id: elem.id, weight: 1 }; 38 | }); 39 | return oldE; 40 | }, {}); 41 | 42 | const dist = graph.nodes.reduce((map: MyDictionary, currentNode: GraphNode) => { 43 | map[currentNode.id] = Infinity; 44 | return map; 45 | }, {}); 46 | 47 | dist[source.id] = 0; 48 | pq.add({ id: source.id, weight: dist[source.id] }); 49 | const parent: ParentVectorType = {}; 50 | const visitedNodes: GraphNode[] = []; 51 | 52 | while (!pq.isEmpty) { 53 | const currentBest = pq.pop(); 54 | 55 | if (!currentBest) continue; 56 | 57 | if (currentBest?.weight !== dist[currentBest.id]) { 58 | continue; 59 | } 60 | visitedNodes.push({ ...currentBest }); 61 | 62 | if (currentBest.id === destination.id) { 63 | break; 64 | } 65 | 66 | graph.edges[currentBest.id].forEach((adj: GraphNode) => { 67 | if (adj.weight && dist[adj.id] > dist[currentBest.id] + adj.weight) { 68 | dist[adj.id] = dist[currentBest.id] + adj.weight; 69 | pq.add({ id: adj.id, weight: dist[adj.id] }); 70 | parent[adj.id] = { ...currentBest }; 71 | } 72 | }); 73 | } 74 | if (visitedNodes[visitedNodes.length - 1].id === destination.id) { 75 | visitedNodes.pop(); 76 | } 77 | visitedNodes.shift(); 78 | 79 | return { 80 | visitedNodes: visitedNodes, 81 | parentVector: parent, 82 | }; 83 | }; 84 | -------------------------------------------------------------------------------- /src/algorithms/graph-algorithms/best-first-search.ts: -------------------------------------------------------------------------------- 1 | import { GraphState } from '../../store/graph/state'; 2 | import { GraphAlgOutput } from '../../utils/types/graph-types/graph-results-types'; 3 | import { computeDistance, fromIndexToPair } from '../../utils/graph-utils-functions'; 4 | import { GraphNode, Graph, ParentVectorType, Edges } from './graph'; 5 | 6 | export const bestFirstSearch = ( 7 | startNode: GraphNode, 8 | destinationNode: GraphNode, 9 | graphState: GraphState, 10 | ): GraphAlgOutput => { 11 | startNode = { ...startNode, weight: 0 }; 12 | destinationNode = { ...destinationNode, weight: 1 }; 13 | const graph: Graph = { 14 | numberOfNodes: graphState.numberOfNodes, 15 | nodes: graphState.nodes, 16 | edges: graphState.edges, 17 | }; 18 | graph.nodes = graph.nodes.map((elem: GraphNode) => { 19 | return { id: elem.id, weight: 1 }; 20 | }); 21 | 22 | graph.edges = Object.keys(graph.edges).reduce((oldE: Edges, key: string) => { 23 | oldE[key] = graph.edges[key].map((elem: GraphNode) => { 24 | if (elem.weight) { 25 | return elem; 26 | } 27 | return { id: elem.id, weight: 1 }; 28 | }); 29 | return oldE; 30 | }, {}); 31 | 32 | const queue: GraphNode[] = [startNode]; 33 | const visitedNodes: GraphNode[] = [startNode]; 34 | const visitedInOrder: GraphNode[] = []; 35 | const parent: ParentVectorType = {}; 36 | 37 | while (queue.length > 0) { 38 | queue.sort((node1: GraphNode, node2: GraphNode) => { 39 | const p1 = fromIndexToPair(parseInt(node1.id, 10), graphState.width); 40 | const p2 = fromIndexToPair(parseInt(node2.id, 10), graphState.width); 41 | const dest = fromIndexToPair(parseInt(destinationNode.id, 10), graphState.width); 42 | 43 | return computeDistance(p1, dest) - computeDistance(p2, dest); 44 | }); 45 | const currentNode = { ...queue[0] }; 46 | visitedInOrder.push(currentNode); 47 | queue.shift(); 48 | if (currentNode.id === destinationNode.id) { 49 | break; 50 | } 51 | 52 | graph.edges[currentNode.id].forEach((elem: GraphNode) => { 53 | let exists = false; 54 | visitedNodes.forEach((visitedNode: GraphNode) => { 55 | if (visitedNode.id === elem.id) { 56 | exists = true; 57 | } 58 | }); 59 | if (!exists && currentNode.weight !== undefined) { 60 | queue.push({ ...elem, weight: 1 + currentNode.weight }); 61 | parent[elem.id] = { ...currentNode }; 62 | visitedNodes.push({ ...elem }); 63 | } 64 | }); 65 | } 66 | visitedInOrder.shift(); 67 | if (visitedInOrder[visitedInOrder.length - 1].id === destinationNode.id) { 68 | visitedInOrder.pop(); 69 | } 70 | 71 | return { 72 | visitedNodes: visitedInOrder, 73 | parentVector: parent, 74 | }; 75 | }; 76 | -------------------------------------------------------------------------------- /src/algorithms/graph-algorithms/bfs.ts: -------------------------------------------------------------------------------- 1 | import { GraphAlgOutput } from '../../utils/types/graph-types/graph-results-types'; 2 | import { GraphNode, Graph, ParentVectorType, Edges } from './graph'; 3 | 4 | export const bfs = (startNode: GraphNode, destinationNode: GraphNode, graph: Graph): GraphAlgOutput => { 5 | startNode = { ...startNode, weight: 0 }; 6 | destinationNode = { ...destinationNode, weight: 1 }; 7 | graph.nodes = graph.nodes.map((elem: GraphNode) => { 8 | return { id: elem.id, weight: 1 }; 9 | }); 10 | 11 | graph.edges = Object.keys(graph.edges).reduce((oldE: Edges, key: string) => { 12 | oldE[key] = graph.edges[key].map((elem: GraphNode) => { 13 | if (elem.weight) { 14 | return elem; 15 | } 16 | return { id: elem.id, weight: 1 }; 17 | }); 18 | return oldE; 19 | }, {}); 20 | 21 | const queue: GraphNode[] = [startNode]; 22 | const visitedNodes: GraphNode[] = [startNode]; 23 | const visitedInOrder: GraphNode[] = []; 24 | const parent: ParentVectorType = {}; 25 | 26 | while (queue.length > 0) { 27 | const currentNode = { ...queue[0] }; 28 | visitedInOrder.push(currentNode); 29 | queue.shift(); 30 | if (currentNode.id === destinationNode.id) { 31 | break; 32 | } 33 | 34 | graph.edges[currentNode.id].forEach((elem: GraphNode) => { 35 | let exists = false; 36 | visitedNodes.forEach((visitedNode: GraphNode) => { 37 | if (visitedNode.id === elem.id) { 38 | exists = true; 39 | } 40 | }); 41 | if (!exists && currentNode.weight !== undefined) { 42 | queue.push({ ...elem, weight: 1 + currentNode.weight }); 43 | parent[elem.id] = { ...currentNode }; 44 | visitedNodes.push({ ...elem }); 45 | } 46 | }); 47 | } 48 | visitedInOrder.shift(); 49 | if (visitedInOrder[visitedInOrder.length - 1].id === destinationNode.id) { 50 | visitedInOrder.pop(); 51 | } 52 | return { 53 | visitedNodes: visitedInOrder, 54 | parentVector: parent, 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /src/algorithms/graph-algorithms/dijkstra.ts: -------------------------------------------------------------------------------- 1 | import { GraphAlgOutput } from '../../utils/types/graph-types/graph-results-types'; 2 | import { Edges, Graph, GraphNode, ParentVectorType } from './graph'; 3 | import { Heap } from 'ts-heap'; 4 | import { MyDictionary } from '../../utils/types/app-types/dictionary'; 5 | 6 | const heapComparator = (node1: GraphNode, node2: GraphNode): number => { 7 | if (node1.weight && node2.weight) { 8 | return node1.weight - node2.weight; 9 | } 10 | return 0; 11 | }; 12 | 13 | export const dijkstra = (source: GraphNode, destination: GraphNode, graph: Graph): GraphAlgOutput => { 14 | const pq: Heap = new Heap(heapComparator); 15 | 16 | graph.nodes = graph.nodes.map((elem: GraphNode) => { 17 | if (elem.weight) { 18 | return elem; 19 | } 20 | return { id: elem.id, weight: 1 }; 21 | }); 22 | graph.edges = Object.keys(graph.edges).reduce((oldE: Edges, key: string) => { 23 | oldE[key] = graph.edges[key].map((elem: GraphNode) => { 24 | if (elem.weight) { 25 | return elem; 26 | } 27 | return { id: elem.id, weight: 1 }; 28 | }); 29 | return oldE; 30 | }, {}); 31 | 32 | const dist = graph.nodes.reduce((map: MyDictionary, currentNode: GraphNode) => { 33 | map[currentNode.id] = Infinity; 34 | return map; 35 | }, {}); 36 | 37 | dist[source.id] = 0; 38 | pq.add({ id: source.id, weight: dist[source.id] }); 39 | const parent: ParentVectorType = {}; 40 | const visitedNodes: GraphNode[] = []; 41 | 42 | while (!pq.isEmpty) { 43 | const currentBest = pq.pop(); 44 | 45 | if (!currentBest) continue; 46 | 47 | if (currentBest?.weight !== dist[currentBest.id]) { 48 | continue; 49 | } 50 | visitedNodes.push({ ...currentBest }); 51 | 52 | if (currentBest.id === destination.id) { 53 | break; 54 | } 55 | 56 | graph.edges[currentBest.id].forEach((adj: GraphNode) => { 57 | if (adj.weight && dist[adj.id] > dist[currentBest.id] + adj.weight) { 58 | dist[adj.id] = dist[currentBest.id] + adj.weight; 59 | pq.add({ id: adj.id, weight: dist[adj.id] }); 60 | parent[adj.id] = { ...currentBest }; 61 | } 62 | }); 63 | } 64 | if (visitedNodes[visitedNodes.length - 1].id === destination.id) { 65 | visitedNodes.pop(); 66 | } 67 | visitedNodes.shift(); 68 | 69 | return { 70 | visitedNodes: visitedNodes, 71 | parentVector: parent, 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /src/algorithms/graph-algorithms/graph.ts: -------------------------------------------------------------------------------- 1 | import { MyDictionary } from '../../utils/types/app-types/dictionary'; 2 | 3 | export type GraphNode = { 4 | id: string; 5 | weight?: number; 6 | }; 7 | 8 | export type Edges = MyDictionary; 9 | 10 | export type Graph = { 11 | numberOfNodes: number; 12 | nodes: GraphNode[]; 13 | edges: Edges; 14 | }; 15 | 16 | export type ParentVectorType = MyDictionary; 17 | -------------------------------------------------------------------------------- /src/algorithms/sorting-algorithms/bubble-sort.ts: -------------------------------------------------------------------------------- 1 | import { SortingState } from '../../store/sorting/state'; 2 | import { copyNumbersImmutable } from '../../utils/sorting-utils-functions'; 3 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 4 | import { 5 | CURRENT_INDEX, 6 | FINISHED_VISITING_INDEX, 7 | PLACED_NUMBER, 8 | SortingAlgorithmResult, 9 | SortingOutputElementType, 10 | SWAPPED_PAIR, 11 | SWAP_PAIR, 12 | } from '../../utils/types/sorting-types/sorting-results-types'; 13 | 14 | export const bubbleSort = (sortingState: SortingState): SortingAlgorithmResult => { 15 | const outputList: SortingOutputElementType[] = []; 16 | 17 | const numbers = copyNumbersImmutable(sortingState.sortingList.numberList).map( 18 | (elem: ArrayStackType) => elem.number, 19 | ); 20 | 21 | let sorted = false; 22 | let current = 0; 23 | let i = 0; 24 | do { 25 | sorted = true; 26 | for (i = 0; i < numbers.length - 1 - current; ++i) { 27 | outputList.push({ index: i, type: CURRENT_INDEX }); 28 | if (numbers[i] > numbers[i + 1]) { 29 | outputList.push({ firstIndex: i, secondIndex: i + 1, type: SWAP_PAIR }); 30 | const tmp = numbers[i]; 31 | numbers[i] = numbers[i + 1]; 32 | numbers[i + 1] = tmp; 33 | sorted = false; 34 | outputList.push({ firstIndex: i, secondIndex: i + 1, type: SWAPPED_PAIR }); 35 | } 36 | outputList.push({ index: i, type: FINISHED_VISITING_INDEX }); 37 | } 38 | outputList.push({ index: i, type: PLACED_NUMBER }); 39 | current += 1; 40 | } while (!sorted); 41 | for (let index = 0; index < numbers.length; ++index) { 42 | outputList.push({ index: index, type: PLACED_NUMBER }); 43 | } 44 | return { 45 | output: outputList, 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /src/algorithms/sorting-algorithms/heap-sort.ts: -------------------------------------------------------------------------------- 1 | import { SortingState } from '../../store/sorting/state'; 2 | import { copyNumbersImmutable } from '../../utils/sorting-utils-functions'; 3 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 4 | import { 5 | CURRENT_INDEX, 6 | FINISHED_VISITING_INDEX, 7 | PLACED_NUMBER, 8 | SortingAlgorithmResult, 9 | SortingOutputElementType, 10 | SWAPPED_PAIR, 11 | SWAP_PAIR, 12 | } from '../../utils/types/sorting-types/sorting-results-types'; 13 | 14 | class Heap { 15 | readonly heapArr: number[]; 16 | readonly heapSize: number; 17 | readonly resList: SortingOutputElementType[]; 18 | private currentSize: number; 19 | 20 | constructor(numbers: number[]) { 21 | this.heapSize = numbers.length; 22 | this.currentSize = numbers.length; 23 | this.heapArr = [...numbers]; 24 | this.resList = []; 25 | this.heapify(); 26 | } 27 | 28 | private heapify = (): void => { 29 | const currIdx = ((this.currentSize / 2) | 0) - 1; 30 | for (let i = currIdx; i >= 0; i--) { 31 | this.resList.push({ type: CURRENT_INDEX, index: i }); 32 | this.heapifyDown(i); 33 | this.resList.push({ type: FINISHED_VISITING_INDEX, index: i }); 34 | } 35 | }; 36 | 37 | public heapSort = (): void => { 38 | for (let i = 0; i < this.heapSize; i++) { 39 | this.resList.push({ type: SWAP_PAIR, firstIndex: 0, secondIndex: this.currentSize - 1 }); 40 | const tmp = this.heapArr[0]; 41 | this.heapArr[0] = this.heapArr[this.currentSize - 1]; 42 | this.heapArr[this.currentSize - 1] = tmp; 43 | this.resList.push({ type: SWAPPED_PAIR, firstIndex: 0, secondIndex: this.currentSize - 1 }); 44 | this.resList.push({ type: PLACED_NUMBER, index: this.currentSize - 1 }); 45 | this.currentSize--; 46 | this.heapifyDown(0); 47 | } 48 | }; 49 | 50 | private heapifyDown = (index: number): void => { 51 | let largest = index; 52 | const leftChild = 2 * index + 1; 53 | const rightChild = 2 * index + 2; 54 | 55 | if (leftChild < this.currentSize && this.heapArr[largest] < this.heapArr[leftChild]) { 56 | largest = leftChild; 57 | } 58 | 59 | if (rightChild < this.currentSize && this.heapArr[largest] < this.heapArr[rightChild]) { 60 | largest = rightChild; 61 | } 62 | 63 | if (largest !== index) { 64 | this.resList.push({ type: SWAP_PAIR, firstIndex: largest, secondIndex: index }); 65 | const tmp = this.heapArr[index]; 66 | this.heapArr[index] = this.heapArr[largest]; 67 | this.heapArr[largest] = tmp; 68 | this.resList.push({ type: SWAPPED_PAIR, firstIndex: largest, secondIndex: index }); 69 | this.heapifyDown(largest); 70 | } 71 | }; 72 | } 73 | export const heapSort = (sortingState: SortingState): SortingAlgorithmResult => { 74 | const numbers = copyNumbersImmutable(sortingState.sortingList.numberList).map( 75 | (elem: ArrayStackType) => elem.number, 76 | ); 77 | 78 | const heap = new Heap(numbers); 79 | heap.heapSort(); 80 | return { 81 | output: heap.resList, 82 | }; 83 | }; 84 | -------------------------------------------------------------------------------- /src/algorithms/sorting-algorithms/merge-sort.ts: -------------------------------------------------------------------------------- 1 | import { SortingState } from '../../store/sorting/state'; 2 | import { copyNumbersImmutable } from '../../utils/sorting-utils-functions'; 3 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 4 | import { 5 | CURRENT_INDEX, 6 | FINISHED_LEFT_TO_MERGE, 7 | FINISHED_MERGE_PAIR, 8 | FINISHED_VISITING_INDEX, 9 | LEFT_TO_MERGE, 10 | MERGE_PAIR, 11 | PLACED_NUMBER, 12 | SortingAlgorithmResult, 13 | SortingOutputElementType, 14 | } from '../../utils/types/sorting-types/sorting-results-types'; 15 | 16 | const merge = (numbers: number[], start: number, mid: number, end: number, resList: SortingOutputElementType[]) => { 17 | const firstHalf = []; 18 | const secondHalf = []; 19 | for (let i = start; i <= mid; ++i) { 20 | firstHalf.push(numbers[i]); 21 | } 22 | for (let i = mid + 1; i <= end; ++i) { 23 | secondHalf.push(numbers[i]); 24 | } 25 | 26 | let i = 0; 27 | let j = 0; 28 | let k = start; 29 | const localMergeOps: SortingOutputElementType[] = []; 30 | while (i < firstHalf.length && j < secondHalf.length) { 31 | if (firstHalf[i] < secondHalf[j]) { 32 | resList.push({ type: MERGE_PAIR, firstIndex: start + i, secondIndex: mid + 1 + j }); 33 | numbers[k] = firstHalf[i]; 34 | localMergeOps.push({ 35 | type: FINISHED_MERGE_PAIR, 36 | firstIndex: start + i, 37 | secondIndex: mid + 1 + j, 38 | currentIndex: k, 39 | placedNumber: firstHalf[i], 40 | }); 41 | i += 1; 42 | } else { 43 | resList.push({ type: MERGE_PAIR, firstIndex: start + i, secondIndex: mid + 1 + j }); 44 | numbers[k] = secondHalf[j]; 45 | localMergeOps.push({ 46 | type: FINISHED_MERGE_PAIR, 47 | firstIndex: start + i, 48 | secondIndex: mid + 1 + j, 49 | currentIndex: k, 50 | placedNumber: secondHalf[j], 51 | }); 52 | j += 1; 53 | } 54 | k += 1; 55 | } 56 | 57 | localMergeOps.forEach((el: SortingOutputElementType) => { 58 | resList.push(el); 59 | }); 60 | 61 | while (i < firstHalf.length) { 62 | resList.push({ type: LEFT_TO_MERGE, indexLeftToMerge: start + i }); 63 | numbers[k] = firstHalf[i]; 64 | resList.push({ 65 | type: FINISHED_LEFT_TO_MERGE, 66 | indexLeftToMerge: start + i, 67 | currentIndex: k, 68 | placedNumber: firstHalf[i], 69 | }); 70 | i++; 71 | k++; 72 | } 73 | 74 | while (j < secondHalf.length) { 75 | resList.push({ type: LEFT_TO_MERGE, indexLeftToMerge: mid + 1 + j }); 76 | numbers[k] = secondHalf[j]; 77 | resList.push({ 78 | type: FINISHED_LEFT_TO_MERGE, 79 | indexLeftToMerge: mid + 1 + j, 80 | currentIndex: k, 81 | placedNumber: secondHalf[j], 82 | }); 83 | j++; 84 | k++; 85 | } 86 | }; 87 | 88 | const mergeSortAux = (numbers: number[], start: number, end: number, resList: SortingOutputElementType[]): void => { 89 | if (start >= end) { 90 | return; 91 | } 92 | const mid = ((start + end) / 2) | 0; 93 | resList.push({ index: mid, type: CURRENT_INDEX }); 94 | mergeSortAux(numbers, start, mid, resList); 95 | mergeSortAux(numbers, mid + 1, end, resList); 96 | merge(numbers, start, mid, end, resList); 97 | resList.push({ index: mid, type: FINISHED_VISITING_INDEX }); 98 | }; 99 | 100 | export const mergeSort = (sortingState: SortingState): SortingAlgorithmResult => { 101 | const numbers = copyNumbersImmutable(sortingState.sortingList.numberList).map( 102 | (elem: ArrayStackType) => elem.number, 103 | ); 104 | const resList: SortingOutputElementType[] = []; 105 | mergeSortAux(numbers, 0, numbers.length - 1, resList); 106 | numbers.forEach((_, i) => { 107 | resList.push({ type: PLACED_NUMBER, index: i }); 108 | }); 109 | const output = { 110 | output: [...resList], 111 | }; 112 | return output; 113 | }; 114 | -------------------------------------------------------------------------------- /src/algorithms/sorting-algorithms/quick-sort.ts: -------------------------------------------------------------------------------- 1 | import { SortingState } from '../../store/sorting/state'; 2 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 3 | import { 4 | CURRENT_INDEX, 5 | FINISHED_VISITING_INDEX, 6 | PLACED_NUMBER, 7 | SortingAlgorithmResult, 8 | SortingOutputElementType, 9 | SWAPPED_PAIR, 10 | SWAP_PAIR, 11 | } from '../../utils/types/sorting-types/sorting-results-types'; 12 | 13 | const pivoting = (arr: number[], low: number, high: number, output: SortingOutputElementType[]): number => { 14 | const piv = arr[high]; 15 | output.push({ index: high, type: CURRENT_INDEX }); 16 | let i = low - 1; 17 | 18 | for (let j = low; j <= high - 1; j++) { 19 | if (arr[j] < piv) { 20 | i++; 21 | output.push({ firstIndex: i, secondIndex: j, type: SWAP_PAIR }); 22 | const tmp = arr[i]; 23 | arr[i] = arr[j]; 24 | arr[j] = tmp; 25 | output.push({ firstIndex: i, secondIndex: j, type: SWAPPED_PAIR }); 26 | } 27 | } 28 | output.push({ firstIndex: i + 1, secondIndex: high, type: SWAP_PAIR }); 29 | const tmp = arr[i + 1]; 30 | arr[i + 1] = arr[high]; 31 | arr[high] = tmp; 32 | output.push({ firstIndex: i + 1, secondIndex: high, type: SWAPPED_PAIR }); 33 | 34 | output.push({ index: high, type: FINISHED_VISITING_INDEX }); 35 | output.push({ index: i + 1, type: PLACED_NUMBER }); 36 | 37 | return i + 1; 38 | }; 39 | 40 | const quickSortAux = (numbers: number[], left: number, right: number, output: SortingOutputElementType[]): void => { 41 | if (left < right) { 42 | const pi = pivoting(numbers, left, right, output); 43 | 44 | quickSortAux(numbers, left, pi - 1, output); 45 | quickSortAux(numbers, pi + 1, right, output); 46 | } 47 | if (left === right) { 48 | output.push({ index: left, type: PLACED_NUMBER }); 49 | } 50 | }; 51 | 52 | export const quickSort = (sortingState: SortingState): SortingAlgorithmResult => { 53 | const output: SortingOutputElementType[] = []; 54 | 55 | const numbers = sortingState.sortingList.numberList.map((el: ArrayStackType) => el.number); 56 | quickSortAux(numbers, 0, numbers.length - 1, output); 57 | 58 | return { 59 | output: output, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/algorithms/sorting-algorithms/sorting.ts: -------------------------------------------------------------------------------- 1 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 2 | 3 | export type SortingList = { 4 | numberList: ArrayStackType[]; 5 | }; 6 | -------------------------------------------------------------------------------- /src/assets/icons/linkedinBlue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/icons/linkedinBlue.png -------------------------------------------------------------------------------- /src/assets/icons/linkedinWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/icons/linkedinWhite.png -------------------------------------------------------------------------------- /src/assets/images/destination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/destination.png -------------------------------------------------------------------------------- /src/assets/images/homepage/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/homepage/graph.png -------------------------------------------------------------------------------- /src/assets/images/logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/logo/logo.png -------------------------------------------------------------------------------- /src/assets/images/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/simple.png -------------------------------------------------------------------------------- /src/assets/images/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/start.png -------------------------------------------------------------------------------- /src/assets/images/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/wall.png -------------------------------------------------------------------------------- /src/assets/images/weight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/assets/images/weight.png -------------------------------------------------------------------------------- /src/components/AlgPropSelector/AlgPropSelector.module.css: -------------------------------------------------------------------------------- 1 | .select { 2 | margin-right: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/AlgPropSelector/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dropdown } from 'semantic-ui-react'; 3 | import { speedStrToSpeed } from '../../utils/app-utils-functions'; 4 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 5 | import { AlgDropdownOption, AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 6 | import { speedDropdownOption } from '../../utils/types/app-types/consts'; 7 | import classes from './AlgPropSelector.module.css'; 8 | 9 | export type AlgPropSelectorProps = { 10 | algOptions: AlgDropdownOption[]; 11 | speedOptions: speedDropdownOption[]; 12 | running: boolean; 13 | setSelectedAlg: (alg: AlgorithmType) => void; 14 | setSpeed: (speed: SpeedType) => void; 15 | algStringToAlgType: (algName: string) => AlgorithmType; 16 | }; 17 | 18 | const AlgPropSelector = (props: AlgPropSelectorProps): JSX.Element => { 19 | return ( 20 | <> 21 | { 27 | if (data.value) props.setSelectedAlg(props.algStringToAlgType(data.value.toString())); 28 | }} 29 | disabled={props.running} 30 | /> 31 | { 37 | if (data.value) props.setSpeed(speedStrToSpeed(data.value.toString())); 38 | }} 39 | disabled={props.running} 40 | /> 41 | 42 | ); 43 | }; 44 | 45 | export default AlgPropSelector; 46 | -------------------------------------------------------------------------------- /src/components/GithubStarCount/GithubStarCount.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GeorgianBadita/algorithm-visualizer/d07b96af042e416658405d2f1a78da37366c5a71/src/components/GithubStarCount/GithubStarCount.module.css -------------------------------------------------------------------------------- /src/components/GithubStarCount/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './GithubStarCount.module.css'; 3 | 4 | const GithubStarCount = (): JSX.Element => { 5 | return ( 6 | 18 | ); 19 | }; 20 | 21 | export default GithubStarCount; 22 | -------------------------------------------------------------------------------- /src/components/Graph/Graph.module.css: -------------------------------------------------------------------------------- 1 | .graph { 2 | margin-left: auto; 3 | margin-right: auto; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Graph/Node/Node.module.css: -------------------------------------------------------------------------------- 1 | .node { 2 | background: #23282c; 3 | border: 1px solid #7b7b7b; 4 | float: left; 5 | line-height: 30px; 6 | height: 30px; 7 | text-align: center; 8 | width: 30px; 9 | cursor: pointer; 10 | border-radius: 5px; 11 | color: #ece4e4; 12 | font-weight: 1000; 13 | } 14 | 15 | .node:focus { 16 | outline: none; 17 | } 18 | 19 | .source { 20 | background-image: url('../../../assets/images/start.png'); 21 | background-position: center; 22 | background-repeat: no-repeat; 23 | background-size: contain; 24 | animation-name: specialNodes; 25 | animation-duration: 2s; 26 | animation-timing-function: ease-out; 27 | animation-direction: alternate; 28 | animation-iteration-count: 1; 29 | animation-play-state: running; 30 | animation-fill-mode: forwards; 31 | } 32 | 33 | .destination { 34 | background-image: url('../../../assets/images/destination.png'); 35 | background-position: center; 36 | background-repeat: no-repeat; 37 | background-size: contain; 38 | animation-name: specialNodes; 39 | animation-duration: 2s; 40 | animation-timing-function: ease-out; 41 | animation-direction: alternate; 42 | animation-iteration-count: 1; 43 | animation-play-state: running; 44 | animation-fill-mode: forwards; 45 | } 46 | 47 | .wall { 48 | background: transparent; 49 | background-image: url('../../../assets/images/wall.png'); 50 | background-position: center; 51 | background-repeat: no-repeat; 52 | background-size: contain; 53 | animation-name: specialNodes; 54 | animation-duration: 0.5s; 55 | animation-timing-function: ease-out; 56 | animation-direction: alternate; 57 | animation-iteration-count: 1; 58 | animation-play-state: running; 59 | animation-fill-mode: forwards; 60 | } 61 | 62 | .weight { 63 | background: url('../../../assets/images/weight.png'); 64 | background-position: center; 65 | background-repeat: no-repeat; 66 | background-size: contain; 67 | animation-name: specialNodes; 68 | animation-duration: 0.5s; 69 | animation-timing-function: ease-out; 70 | animation-direction: alternate; 71 | animation-iteration-count: 1; 72 | animation-play-state: running; 73 | animation-fill-mode: forwards; 74 | z-index: -1; 75 | } 76 | 77 | .visitedWeight { 78 | background: url('../../../assets/images/weight.png') repeat, radial-gradient(circle, #2193b0 0%, #6dd5ed 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 79 | background-position: center; 80 | background-repeat: no-repeat; 81 | background-size: contain; 82 | animation-name: specialNodes; 83 | animation-duration: 0.5s; 84 | animation-timing-function: ease-out; 85 | animation-direction: alternate; 86 | animation-iteration-count: 1; 87 | animation-play-state: running; 88 | animation-fill-mode: forwards; 89 | color: #fff; 90 | z-index: -1; 91 | } 92 | 93 | .visitedShortestPathWeight { 94 | background: url('../../../assets/images/weight.png') repeat, radial-gradient(circle, #fceabb 0%, #f8b500 100%); 95 | background-position: center; 96 | background-repeat: no-repeat; 97 | background-size: contain; 98 | animation-name: specialNodes; 99 | animation-duration: 0.5s; 100 | animation-timing-function: ease-out; 101 | animation-direction: alternate; 102 | animation-iteration-count: 1; 103 | animation-play-state: running; 104 | animation-fill-mode: forwards; 105 | z-index: -1; 106 | color: #fff; 107 | } 108 | 109 | .visited { 110 | background: radial-gradient(circle, #2193b0 0%, #6dd5ed 100%); 111 | animation-name: specialNodes; 112 | animation-duration: 0.3s; 113 | animation-timing-function: ease-out; 114 | animation-direction: alternate; 115 | animation-iteration-count: 1; 116 | animation-play-state: running; 117 | animation-fill-mode: forwards; 118 | z-index: -1; 119 | } 120 | 121 | .shortestPath { 122 | background-image: radial-gradient(circle, #fceabb 0%, #f8b500 100%); 123 | animation-name: specialNodes; 124 | animation-duration: 0.5s; 125 | animation-timing-function: ease-out; 126 | animation-direction: alternate; 127 | animation-iteration-count: 1; 128 | animation-play-state: running; 129 | animation-fill-mode: forwards; 130 | z-index: -1; 131 | color: black; 132 | } 133 | 134 | @keyframes specialNodes { 135 | 0% { 136 | transform: scale(0.3); 137 | } 138 | 139 | 50% { 140 | transform: scale(1.2); 141 | } 142 | 143 | 100% { 144 | transform: scale(1); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/components/Graph/Node/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | DESTINATION_NODE, 4 | NodeType, 5 | SHORTEST_PATH_NODE, 6 | SOURCE_NODE, 7 | VISITED_NODE, 8 | VISITED_WEIGHT_NODE, 9 | VISITED_WEIGHT_SHORTEST_PATH_NODE, 10 | WALL_NODE, 11 | WEIGHTED_NODE, 12 | } from '../../../utils/types/graph-types/node-type'; 13 | import classes from './Node.module.css'; 14 | 15 | type NodeProps = { 16 | onMouseEnter: (x: number, y: number) => void; 17 | onMouseDown: (x: number, y: number) => void; 18 | nodeType: NodeType; 19 | row: number; 20 | col: number; 21 | weight?: number; 22 | }; 23 | 24 | const Node = (props: NodeProps): JSX.Element => { 25 | const cssClasses = [classes.node]; 26 | if (props.nodeType === SOURCE_NODE) { 27 | cssClasses.push(classes.source); 28 | } else if (props.nodeType === DESTINATION_NODE) { 29 | cssClasses.push(classes.destination); 30 | } else if (props.nodeType === WALL_NODE) { 31 | cssClasses.push(classes.wall); 32 | } else if (props.nodeType === WEIGHTED_NODE) { 33 | cssClasses.push(classes.weight); 34 | } else if (props.nodeType === VISITED_NODE) { 35 | cssClasses.push(classes.visited); 36 | } else if (props.nodeType === SHORTEST_PATH_NODE) { 37 | cssClasses.push(classes.shortestPath); 38 | } else if (props.nodeType === VISITED_WEIGHT_NODE) { 39 | cssClasses.push(classes.visitedWeight); 40 | } else if (props.nodeType === VISITED_WEIGHT_SHORTEST_PATH_NODE) { 41 | cssClasses.push(classes.visitedShortestPathWeight); 42 | } 43 | return ( 44 |
props.onMouseDown(props.row, props.col)} 46 | onMouseEnter={() => props.onMouseEnter(props.row, props.col)} 47 | className={cssClasses.join(' ')} 48 | > 49 | {props.weight || ''} 50 |
51 | ); 52 | }; 53 | export default React.memo(Node); 54 | -------------------------------------------------------------------------------- /src/components/Graph/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | 3 | import Node from './Node/index'; 4 | import classes from './Graph.module.css'; 5 | 6 | import { 7 | checkCanPutWeight, 8 | copyTableImmutable, 9 | getNewGrid, 10 | getVisitedNodes, 11 | reduxGraphUpdateDispatchHelper, 12 | wasAlgorithmRunning, 13 | } from '../../utils/graph-utils-functions'; 14 | import { GraphNode } from '../../algorithms/graph-algorithms/graph'; 15 | import { GraphActionTypes } from '../../store/graph/types'; 16 | import { 17 | DESTINATION_NODE_BUTTON, 18 | NodeTypeButtonType, 19 | SOURCE_NODE_BUTTON, 20 | } from '../../utils/types/graph-types/node-type-button-type'; 21 | import { toast } from 'react-toastify'; 22 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 23 | import { TableNodeType } from '../../utils/types/graph-types/table-node-type'; 24 | import { 25 | DESTINATION_NODE, 26 | SHORTEST_PATH_NODE, 27 | SIMPLE_NODE, 28 | SOURCE_NODE, 29 | VISITED_NODE, 30 | VISITED_WEIGHT_NODE, 31 | VISITED_WEIGHT_SHORTEST_PATH_NODE, 32 | WEIGHTED_NODE, 33 | } from '../../utils/types/graph-types/node-type'; 34 | import { GraphAlgorithmResult, Pair } from '../../utils/types/graph-types/graph-results-types'; 35 | import { GraphState } from '../../store/graph/state'; 36 | 37 | const DEFAULT_WEIGHT_VALUE = 10; 38 | 39 | type GraphProps = { 40 | height: number; 41 | width: number; 42 | table: TableNodeType[][]; 43 | activeNodeTypeButton: NodeTypeButtonType; 44 | selectedAlg: AlgorithmType; 45 | setGraph: (value: TableNodeType[][]) => void; 46 | running: boolean; 47 | changeSourceNode: (newSoruce: GraphNode) => GraphActionTypes; 48 | changeDestinationNode: (newDest: GraphNode) => GraphActionTypes; 49 | deleteNode: (node: GraphNode) => GraphActionTypes; 50 | addNode: (node: GraphNode, table: TableNodeType[][]) => GraphActionTypes; 51 | addWeightedNode: (node: GraphNode, table: TableNodeType[][]) => GraphActionTypes; 52 | setActiveNodeTypeButton: Dispatch>; 53 | graphState: GraphState; 54 | }; 55 | 56 | const Graph = (props: GraphProps): JSX.Element => { 57 | const [isClicked, setIsClicked] = React.useState(false); 58 | const [lastActiveButton, setLastActiveButton] = React.useState(props.activeNodeTypeButton); 59 | const tableRef = React.useRef(props.table); 60 | tableRef.current = props.table; 61 | 62 | const handleRedrawOnDestinationMove = (): void => { 63 | const { visitedNodesInOrder, shortestPath }: GraphAlgorithmResult = getVisitedNodes( 64 | props.selectedAlg, 65 | props.graphState, 66 | ); 67 | const newTable = copyTableImmutable(tableRef.current).map((row: TableNodeType[]) => 68 | row.map((elem: TableNodeType) => { 69 | if (elem.nodeType === VISITED_NODE || elem.nodeType === SHORTEST_PATH_NODE) { 70 | return { nodeType: SIMPLE_NODE } as TableNodeType; 71 | } 72 | if (elem.nodeType === VISITED_WEIGHT_NODE) { 73 | return { nodeType: WEIGHTED_NODE, weight: DEFAULT_WEIGHT_VALUE } as TableNodeType; 74 | } 75 | return elem; 76 | }), 77 | ); 78 | 79 | visitedNodesInOrder.forEach((pair: Pair) => { 80 | const { row, col, weight } = pair; 81 | if (newTable[row][col].nodeType === WEIGHTED_NODE) { 82 | newTable[row][col] = { nodeType: VISITED_WEIGHT_NODE, weight: weight }; 83 | } else if (newTable[row][col].nodeType !== DESTINATION_NODE) { 84 | newTable[row][col] = { nodeType: VISITED_NODE, weight: weight }; 85 | } 86 | }); 87 | 88 | shortestPath.forEach((pair: Pair) => { 89 | const { row, col, weight } = pair; 90 | if (newTable[row][col].nodeType === VISITED_WEIGHT_NODE) { 91 | newTable[row][col] = { nodeType: VISITED_WEIGHT_SHORTEST_PATH_NODE, weight: weight }; 92 | } else if (newTable[row][col].nodeType !== DESTINATION_NODE) { 93 | newTable[row][col] = { nodeType: SHORTEST_PATH_NODE, weight: weight }; 94 | } 95 | }); 96 | props.setGraph(newTable); 97 | }; 98 | 99 | const handleOnMouseEnter = (x: number, y: number): void => { 100 | if (!isClicked) return; 101 | if (!checkCanPutWeight(props.selectedAlg, props.activeNodeTypeButton)) { 102 | toast(`You cannot use weights with ${props.selectedAlg}`); 103 | return; 104 | } 105 | if (props.running) { 106 | toast(`You cannot modify graph while algorithm is running`); 107 | return; 108 | } 109 | 110 | props.setGraph(getNewGrid(tableRef.current, props.activeNodeTypeButton, x, y)); 111 | reduxGraphUpdateDispatchHelper( 112 | tableRef.current, 113 | props.activeNodeTypeButton, 114 | props.changeSourceNode, 115 | props.changeDestinationNode, 116 | props.deleteNode, 117 | props.addNode, 118 | props.addWeightedNode, 119 | x, 120 | y, 121 | props.width, 122 | DEFAULT_WEIGHT_VALUE, 123 | ); 124 | 125 | //TODO: THIS IS AN EXPERIMENTAL FEATURE 126 | if ( 127 | window.experimental && 128 | window.experimental === true && 129 | props.activeNodeTypeButton === DESTINATION_NODE_BUTTON && 130 | wasAlgorithmRunning(tableRef.current) 131 | ) { 132 | setTimeout(() => handleRedrawOnDestinationMove(), 300); 133 | } 134 | }; 135 | 136 | const handleOnMouseUp = () => { 137 | if ( 138 | props.activeNodeTypeButton === SOURCE_NODE_BUTTON || 139 | props.activeNodeTypeButton === DESTINATION_NODE_BUTTON 140 | ) { 141 | props.setActiveNodeTypeButton(lastActiveButton); 142 | } 143 | setIsClicked(false); 144 | }; 145 | 146 | const handleOnMouseDown = (x: number, y: number) => { 147 | if (!checkCanPutWeight(props.selectedAlg, props.activeNodeTypeButton)) { 148 | toast(`You cannot use weights with ${props.selectedAlg}`); 149 | return; 150 | } 151 | if (props.running) { 152 | toast(`You cannot modify graph while algorithm is running`); 153 | return; 154 | } 155 | 156 | //dragging source node 157 | if (tableRef.current[x][y].nodeType === SOURCE_NODE) { 158 | setLastActiveButton(props.activeNodeTypeButton); 159 | props.setActiveNodeTypeButton(SOURCE_NODE_BUTTON); 160 | } else if (tableRef.current[x][y].nodeType === DESTINATION_NODE) { 161 | //dragging destination node 162 | setLastActiveButton(props.activeNodeTypeButton); 163 | props.setActiveNodeTypeButton(DESTINATION_NODE_BUTTON); 164 | } 165 | 166 | props.setGraph(getNewGrid(tableRef.current, props.activeNodeTypeButton, x, y)); 167 | reduxGraphUpdateDispatchHelper( 168 | tableRef.current, 169 | props.activeNodeTypeButton, 170 | props.changeSourceNode, 171 | props.changeDestinationNode, 172 | props.deleteNode, 173 | props.addNode, 174 | props.addWeightedNode, 175 | x, 176 | y, 177 | props.width, 178 | DEFAULT_WEIGHT_VALUE, 179 | ); 180 | 181 | //TODO: THIS IS AN EXPERIMENTAL FEATURE 182 | if ( 183 | window.experimental && 184 | window.experimental === true && 185 | props.activeNodeTypeButton === DESTINATION_NODE_BUTTON && 186 | wasAlgorithmRunning(tableRef.current) 187 | ) { 188 | setTimeout(() => handleRedrawOnDestinationMove(), 300); 189 | } 190 | setIsClicked(true); 191 | }; 192 | 193 | const getGraphData = () => { 194 | return props.table.map((row, x) => ( 195 | 196 | {row.map((tableNode, y) => ( 197 | 198 | 206 | 207 | ))} 208 | 209 | )); 210 | }; 211 | 212 | return ( 213 | handleOnMouseUp()}> 214 | {getGraphData()} 215 |
216 | ); 217 | }; 218 | 219 | export default React.memo(Graph); 220 | -------------------------------------------------------------------------------- /src/components/GraphOptionsButtonGroup/GraphOptionsButtonGrooup.module.css: -------------------------------------------------------------------------------- 1 | .nodeTypeButtonGroup { 2 | margin-top: 100px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | text-align: center; 6 | align-items: center; 7 | } 8 | 9 | .clearButton { 10 | margin-right: 20px; 11 | } 12 | 13 | .select { 14 | margin-right: 20px; 15 | } 16 | 17 | .btn { 18 | display: inline-block; 19 | font-weight: 400; 20 | text-align: center; 21 | white-space: nowrap; 22 | vertical-align: middle; 23 | -webkit-user-select: none; 24 | -moz-user-select: none; 25 | -ms-user-select: none; 26 | user-select: none; 27 | border: 1px solid transparent; 28 | padding: 8px; 29 | font-size: 1rem; 30 | line-height: 1.5; 31 | border-radius: 0.25rem; 32 | transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, 33 | box-shadow 0.15s ease-in-out; 34 | color: #fff; 35 | } 36 | 37 | .btn:hover { 38 | cursor: pointer; 39 | } 40 | 41 | .btn:hover:disabled { 42 | cursor: not-allowed; 43 | } 44 | 45 | .startButton { 46 | margin-right: 20px; 47 | background-color: #7a0194; 48 | } 49 | 50 | .startButton:hover, 51 | .startButton:disabled { 52 | background-color: #67007d; 53 | } 54 | 55 | .startButton:focus { 56 | outline: 0; 57 | } 58 | 59 | .startButton:not(:disabled):not(.disabled):active, 60 | .startButton:not(:disabled):not(.disabled).active, 61 | .startButton.dropdown-toggle { 62 | background-color: #7a0194; 63 | border-color: #7a0194; 64 | } 65 | 66 | .startButton:not(:disabled):not(.disabled):active:focus, 67 | .startButton:not(:disabled):not(.disabled).active:focus, 68 | .startButton.dropdown-toggle:focus { 69 | box-shadow: 0 0 0 0.2rem #7a0194; 70 | } 71 | 72 | .clearButton { 73 | margin-right: 20px; 74 | background-color: #e48031; 75 | } 76 | 77 | .clearButton:hover, 78 | .clearButton:disabled { 79 | background-color: #c5681e; 80 | } 81 | 82 | .clearButton:focus { 83 | outline: 0; 84 | } 85 | 86 | .clearButton:not(:disabled):not(.disabled):active, 87 | .clearButton:not(:disabled):not(.disabled).active, 88 | .clearButton.dropdown-toggle { 89 | background-color: #e48031; 90 | border-color: #e48031; 91 | } 92 | 93 | .clearButton:not(:disabled):not(.disabled):active:focus, 94 | .clearButton:not(:disabled):not(.disabled).active:focus, 95 | .clearButton.dropdown-toggle:focus { 96 | box-shadow: 0 0 0 0.2rem #e48031; 97 | } 98 | 99 | .dropdown { 100 | border-radius: 0.25rem; 101 | color: #000; 102 | margin-right: 20px; 103 | border: 1px solid #fff; 104 | background-color: white; 105 | padding: 9px; 106 | } 107 | -------------------------------------------------------------------------------- /src/components/GraphOptionsButtonGroup/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | import { NodeTypeButtonType } from '../../utils/types/graph-types/node-type-button-type'; 3 | import classes from './GraphOptionsButtonGrooup.module.css'; 4 | 5 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 6 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 7 | import { addNodesDropdownOptions, graphAlgDropdownOptions } from '../../utils/types/graph-types/consts'; 8 | import { speedDropdownOptions } from '../../utils/types/app-types/consts'; 9 | import AlgPropSelector from '../AlgPropSelector'; 10 | import { algNameToAlgType, isGraphAlgorithm } from '../../utils/graph-utils-functions'; 11 | import { BREADTH_FIRST_SEARCH, NO_ALGORITHM } from '../../utils/types/graph-types/graph-algorithm-types'; 12 | import { Dropdown } from 'semantic-ui-react'; 13 | 14 | type NodeTypeButtonGroupProps = { 15 | activeNodeTypeButton?: NodeTypeButtonType; 16 | setActiveNodeTypeButton: Dispatch>; 17 | selectedAlg: AlgorithmType; 18 | running: boolean; 19 | clearApp: () => void; 20 | changeAppRunningState: (newState: boolean) => void; 21 | setSelectedAlg: (alg: AlgorithmType) => void; 22 | setSpeed: (speed: SpeedType) => void; 23 | resetGraphForAlg: () => void; 24 | clearButton: () => void; 25 | }; 26 | 27 | const NodeTypeButtonGroup = (props: NodeTypeButtonGroupProps): JSX.Element => { 28 | const handleOnAlgStart = () => { 29 | props.changeAppRunningState(true); 30 | if (isGraphAlgorithm(props.selectedAlg)) { 31 | props.resetGraphForAlg(); 32 | } 33 | }; 34 | 35 | if (props.selectedAlg === NO_ALGORITHM || !isGraphAlgorithm(props.selectedAlg)) { 36 | props.setSelectedAlg(BREADTH_FIRST_SEARCH); 37 | } 38 | 39 | return ( 40 | <> 41 |
42 | 49 | 57 | props.setActiveNodeTypeButton(data.value as NodeTypeButtonType)} 62 | disabled={props.running && props.running === true} 63 | /> 64 | 70 |
71 | 72 | ); 73 | }; 74 | 75 | export default NodeTypeButtonGroup; 76 | -------------------------------------------------------------------------------- /src/components/LabeledSlider/LabeledSlider.module.css: -------------------------------------------------------------------------------- 1 | .label { 2 | color: white; 3 | } 4 | 5 | .slider { 6 | color: '#fff'; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/LabeledSlider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import RangeSlider from 'react-bootstrap-range-slider'; 3 | import classes from './LabeledSlider.module.css'; 4 | 5 | export type LabeledSliderProps = { 6 | sliderValue: number; 7 | step: number; 8 | min: number; 9 | max: number; 10 | onSliderChange: (newVal: number) => void; 11 | disabled: boolean; 12 | }; 13 | 14 | const LabeledSlider = (props: LabeledSliderProps): JSX.Element => { 15 | return ( 16 | <> 17 | 18 | props.onSliderChange(parseInt(e.target.value as string, 10))} 26 | /> 27 | 28 | ); 29 | }; 30 | 31 | export default LabeledSlider; 32 | -------------------------------------------------------------------------------- /src/components/Navigation/Toolbar/Toolbar.module.css: -------------------------------------------------------------------------------- 1 | .logoDiv { 2 | opacity: 1; 3 | position: absolute; 4 | left: 20px; 5 | top: 14px; 6 | z-index: 1; 7 | } 8 | 9 | .toolbar { 10 | background-color: #666a86; 11 | } 12 | 13 | .logo { 14 | width: 64px; 15 | height: 64px; 16 | } 17 | 18 | .logo:hover { 19 | cursor: pointer; 20 | } 21 | 22 | .menu { 23 | list-style: none; 24 | position: fixed; 25 | top: 20px; 26 | right: 20px; 27 | z-index: 100; 28 | -webkit-font-smoothing: antialiased; 29 | letter-spacing: 1px; 30 | font-size: 1.3em; 31 | } 32 | 33 | .menu li { 34 | display: inline-block; 35 | margin: 10px 0; 36 | } 37 | 38 | .menu a { 39 | color: #fff; 40 | text-decoration: none; 41 | padding: 0 1.1em 1.1em; 42 | } 43 | 44 | .menu a:hover { 45 | cursor: pointer; 46 | } 47 | 48 | .menuHighlight { 49 | color: #ffffff; 50 | background: transparent; 51 | border-width: 2px; 52 | border-radius: 50px; 53 | transition: all 0.2s linear; 54 | line-height: 45px; 55 | } 56 | 57 | .menuHighlight a { 58 | color: #fff; 59 | } 60 | 61 | .menuHighlight:hover { 62 | color: #ffffff; 63 | background: #fff; 64 | border-color: #fff; 65 | } 66 | 67 | li.menuHighlight:hover a { 68 | color: #000; 69 | } 70 | 71 | .linkedin img:hover { 72 | cursor: pointer; 73 | } 74 | -------------------------------------------------------------------------------- /src/components/Navigation/Toolbar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, RouteComponentProps } from 'react-router-dom'; 3 | import logo from '../../../assets/images/logo/logo.png'; 4 | import linkedinWhite from '../../../assets/icons/linkedinWhite.png'; 5 | import linkedinBlue from '../../../assets/icons/linkedinBlue.png'; 6 | import { withRouter } from 'react-router-dom'; 7 | 8 | import classes from './Toolbar.module.css'; 9 | 10 | export interface ToolbarProps extends RouteComponentProps { 11 | clearApp: () => void; 12 | } 13 | 14 | const Toolbar = withRouter( 15 | (props: ToolbarProps): JSX.Element => { 16 | const [currentLinkedinIcon, setCurrentLinkedinIcon] = React.useState(''); 17 | 18 | React.useEffect(() => { 19 | setCurrentLinkedinIcon(linkedinWhite); 20 | }, []); 21 | 22 | const handleOnMouseEnter = () => { 23 | setCurrentLinkedinIcon(linkedinBlue); 24 | }; 25 | 26 | const handleOnMouseLeave = () => { 27 | setCurrentLinkedinIcon(linkedinWhite); 28 | }; 29 | 30 | return ( 31 |
32 |
33 | props.clearApp()} to="/home"> 34 | Logo 35 | 36 |
37 | 71 |
72 | ); 73 | }, 74 | ); 75 | 76 | export default Toolbar; 77 | -------------------------------------------------------------------------------- /src/components/SortOptionsButtonGroup/SortOptionsButtonGroup.module.css: -------------------------------------------------------------------------------- 1 | .sortOptionsButtonGroup { 2 | margin-top: 100px; 3 | margin-left: auto; 4 | margin-right: auto; 5 | text-align: center; 6 | align-items: center; 7 | background-color: #666a86; 8 | } 9 | 10 | .slider { 11 | display: inline-block; 12 | margin-right: 20px; 13 | } 14 | 15 | .btn { 16 | display: inline-block; 17 | font-weight: 400; 18 | text-align: center; 19 | white-space: nowrap; 20 | vertical-align: middle; 21 | -webkit-user-select: none; 22 | -moz-user-select: none; 23 | -ms-user-select: none; 24 | user-select: none; 25 | border: 1px solid transparent; 26 | padding: 8px; 27 | font-size: 1rem; 28 | line-height: 1.5; 29 | border-radius: 0.25rem; 30 | transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, 31 | box-shadow 0.15s ease-in-out; 32 | color: #fff; 33 | } 34 | 35 | .btn:hover { 36 | cursor: pointer; 37 | } 38 | 39 | .btn:hover:disabled { 40 | cursor: not-allowed; 41 | } 42 | 43 | .startButton { 44 | margin-right: 20px; 45 | background-color: #7a0194; 46 | } 47 | 48 | .startButton:hover, 49 | .startButton:disabled { 50 | background-color: #67007d; 51 | } 52 | 53 | .startButton:focus { 54 | outline: 0; 55 | } 56 | 57 | .startButton:not(:disabled):not(.disabled):active, 58 | .startButton:not(:disabled):not(.disabled).active, 59 | .startButton.dropdown-toggle { 60 | background-color: #7a0194; 61 | border-color: #7a0194; 62 | } 63 | 64 | .startButton:not(:disabled):not(.disabled):active:focus, 65 | .startButton:not(:disabled):not(.disabled).active:focus, 66 | .startButton.dropdown-toggle:focus { 67 | box-shadow: 0 0 0 0.2rem #7a0194; 68 | } 69 | 70 | .clearButton { 71 | margin-right: 20px; 72 | background-color: #e48031; 73 | } 74 | 75 | .clearButton:hover, 76 | .clearButton:disabled { 77 | background-color: #c5681e; 78 | } 79 | 80 | .clearButton:focus { 81 | outline: 0; 82 | } 83 | 84 | .clearButton:not(:disabled):not(.disabled):active, 85 | .clearButton:not(:disabled):not(.disabled).active, 86 | .clearButton.dropdown-toggle { 87 | background-color: #e48031; 88 | border-color: #e48031; 89 | } 90 | 91 | .clearButton:not(:disabled):not(.disabled):active:focus, 92 | .clearButton:not(:disabled):not(.disabled).active:focus, 93 | .clearButton.dropdown-toggle:focus { 94 | box-shadow: 0 0 0 0.2rem #e48031; 95 | } 96 | -------------------------------------------------------------------------------- /src/components/SortOptionsButtonGroup/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { isSortingAlgorithm, sortNameToSortType } from '../../utils/sorting-utils-functions'; 3 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 4 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 5 | import { speedDropdownOptions } from '../../utils/types/app-types/consts'; 6 | import { NO_ALGORITHM } from '../../utils/types/graph-types/graph-algorithm-types'; 7 | import { sortingAlgDropdownOptions } from '../../utils/types/sorting-types/consts'; 8 | import { BUBBLE_SORT } from '../../utils/types/sorting-types/sorting-alorithm-types'; 9 | import AlgPropSelector from '../AlgPropSelector'; 10 | import classes from './SortOptionsButtonGroup.module.css'; 11 | import LabeledSlider from '../LabeledSlider'; 12 | 13 | export type SortOptionsButtonGroupProps = { 14 | setSelectedAlg: (alg: AlgorithmType) => void; 15 | setSpeed: (speed: SpeedType) => void; 16 | running: boolean; 17 | selectedAlg: AlgorithmType; 18 | clearApp: () => void; 19 | changeAppRunningState: (newState: boolean) => void; 20 | minLength: number; 21 | maxLength: number; 22 | changeListSize: (newSize: number) => void; 23 | resetList: () => void; 24 | reInitList: () => void; 25 | }; 26 | 27 | const SortOptionsButtonGroup = (props: SortOptionsButtonGroupProps): JSX.Element => { 28 | const regenerateList = () => { 29 | props.clearApp(); 30 | props.resetList(); 31 | }; 32 | 33 | const handleOnAlgStart = () => { 34 | props.changeAppRunningState(true); 35 | if (isSortingAlgorithm(props.selectedAlg)) { 36 | props.reInitList(); 37 | } 38 | }; 39 | 40 | if (props.selectedAlg === NO_ALGORITHM || !isSortingAlgorithm(props.selectedAlg)) { 41 | props.setSelectedAlg(BUBBLE_SORT); 42 | } 43 | 44 | const [sliderValue, setSliderValue] = React.useState(((props.minLength + props.maxLength) / 2) | 0); 45 | 46 | const onSliderChange = (newVal: number): void => { 47 | setSliderValue(newVal); 48 | props.changeListSize(newVal); 49 | }; 50 | 51 | return ( 52 | <> 53 |
54 | 61 | 62 |
63 | 71 |
72 | 80 | 86 |
87 | 88 | ); 89 | }; 90 | 91 | export default SortOptionsButtonGroup; 92 | -------------------------------------------------------------------------------- /src/components/SortingStacks/SortingStack/SortingStack.module.css: -------------------------------------------------------------------------------- 1 | .stack { 2 | display: inline-block; 3 | color: #23282c; 4 | font-weight: bold; 5 | font-size: 13px; 6 | margin-left: 4px; 7 | border-style: solid; 8 | } 9 | 10 | .unvisitedStack { 11 | border-color: #f5f5f5; 12 | background-color: #f5f5f5; 13 | } 14 | 15 | .swappingStack { 16 | border-color: #397bff; 17 | background-color: #397bff; 18 | } 19 | 20 | .currentStack { 21 | border-color: #6435c9; 22 | background-color: #6435c9; 23 | } 24 | 25 | .sortInPlaceStack { 26 | border-color: #ddc9b4; 27 | background-color: #ddc9b4; 28 | } 29 | 30 | .mergingStack { 31 | border-color: #397bff; 32 | background-color: #397bff; 33 | } 34 | 35 | .leftToMergeStack { 36 | border-color: #397bff; 37 | background-color: #397bff; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/SortingStacks/SortingStack/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './SortingStack.module.css'; 3 | import { DEFAULT_STACK_HEIGHT } from '../../../utils/types/sorting-types/sorting-default-values'; 4 | import { 5 | CURRENT_STACK, 6 | LEFT_TO_MERGE_STACK, 7 | MERGING_STACK, 8 | PUT_IN_PLACE, 9 | SortingStackType, 10 | SWAP_STACK, 11 | UNVISITED_STACK, 12 | } from '../../../utils/types/sorting-types/sorting-stack-type'; 13 | import { useWindowSize } from '../../../hooks/hooks'; 14 | 15 | type SortingStackProps = { 16 | height: number; 17 | stackType: SortingStackType; 18 | width: number; 19 | }; 20 | 21 | const NOT_NEED_FOR_SCROLLING_HEIGHT = 935; 22 | 23 | export const SortingStack = (props: SortingStackProps): JSX.Element => { 24 | const [, height] = useWindowSize(); 25 | const cssClasses = [classes.stack]; 26 | if (props.stackType === UNVISITED_STACK) { 27 | cssClasses.push(classes.unvisitedStack); 28 | } 29 | if (props.stackType === SWAP_STACK) { 30 | cssClasses.push(classes.swappingStack); 31 | } 32 | if (props.stackType === CURRENT_STACK) { 33 | cssClasses.push(classes.currentStack); 34 | } 35 | if (props.stackType === PUT_IN_PLACE) { 36 | cssClasses.push(classes.sortInPlaceStack); 37 | } 38 | if (props.stackType === MERGING_STACK) { 39 | cssClasses.push(classes.mergingStack); 40 | } 41 | if (props.stackType === LEFT_TO_MERGE_STACK) { 42 | cssClasses.push(classes.leftToMergeStack); 43 | } 44 | 45 | let stackHeight = DEFAULT_STACK_HEIGHT + 12 * props.height; 46 | if (height && height <= NOT_NEED_FOR_SCROLLING_HEIGHT) { 47 | const diff = (NOT_NEED_FOR_SCROLLING_HEIGHT - height) / 50; 48 | stackHeight = (stackHeight * (1 - diff / 10)) | 0; 49 | } 50 | return ( 51 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/SortingStacks/SortingStacks.module.css: -------------------------------------------------------------------------------- 1 | .sortingStacks { 2 | margin-left: auto; 3 | margin-right: auto; 4 | height: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/SortingStacks/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useWindowSize } from '../../hooks/hooks'; 3 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 4 | import { SortingStack } from './SortingStack'; 5 | import classes from './SortingStacks.module.css'; 6 | 7 | type SortingStacksProps = { 8 | heights: ArrayStackType[]; 9 | }; 10 | 11 | export const SortingStacks = (props: SortingStacksProps): JSX.Element => { 12 | const [width] = useWindowSize(); 13 | const stacksWidth = (width * 0.3) | 0; 14 | const oneStackWidth = (stacksWidth / props.heights.length) | 0; 15 | 16 | return ( 17 |
18 | {props.heights.map((elem: ArrayStackType, index: number) => ( 19 | 20 | ))} 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/containers/AlgVisualizerRouting/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route, Redirect } from 'react-router-dom'; 3 | import GraphContainerAlgorithms from '../GraphContainerAlgorithms'; 4 | import HomePage from '../HomePage'; 5 | import SortingContainerAlgorithms from '../SortingContainerAlgorithms'; 6 | 7 | /** 8 | * Routing component 9 | */ 10 | const AlgVisualizerRouting = (): JSX.Element => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | 21 | export default AlgVisualizerRouting; 22 | -------------------------------------------------------------------------------- /src/containers/GraphContainerAlgorithms/GraphContainerAlgorithms.module.css: -------------------------------------------------------------------------------- 1 | .graphContainerAlgorithms { 2 | align-items: center; 3 | text-align: center; 4 | margin-top: 40px; 5 | } 6 | -------------------------------------------------------------------------------- /src/containers/GraphContainerAlgorithms/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | import Graph from '../../components/Graph'; 3 | import { 4 | SHORTEST_PATH_NODE, 5 | VISITED_NODE, 6 | VISITED_WEIGHT_NODE, 7 | VISITED_WEIGHT_SHORTEST_PATH_NODE, 8 | WEIGHTED_NODE, 9 | } from '../../utils/types/graph-types/node-type'; 10 | import classes from './GraphContainerAlgorithms.module.css'; 11 | import { connect, ConnectedProps } from 'react-redux'; 12 | import { 13 | addNode, 14 | addWeightedNode, 15 | changeDestinationNode, 16 | changeSourceNode, 17 | changeTable, 18 | clearGraph, 19 | deleteNode, 20 | initGraph, 21 | resetGraphForNewAlgorithm, 22 | } from '../../store/graph/actions'; 23 | import { AlgorithmVisualizerState } from '../../store/state'; 24 | import { 25 | algorithmDoesNoatAcceptWeights, 26 | copyTableImmutable, 27 | getVisitedNodes, 28 | isGraphAlgorithm, 29 | } from '../../utils/graph-utils-functions'; 30 | import NodeTypeButtonGroup from '../../components/GraphOptionsButtonGroup'; 31 | import { NodeTypeButtonType, RESTORE_NODE_BUTTON } from '../../utils/types/graph-types/node-type-button-type'; 32 | import { changeAlgorithm, changeRunningState, changeSpeed, clearApp, setUiActions } from '../../store/app/actions'; 33 | import { GraphAlgorithmResult, Pair } from '../../utils/types/graph-types/graph-results-types'; 34 | import { useWindowSize, useWindowSizeDivided } from '../../hooks/hooks'; 35 | import { createErrorToast } from '../../utils/app-utils-functions'; 36 | 37 | const DEFAULT_FIRST_PERIOD_VISITED = 2; 38 | const DEFAULT_INCREMENT_VISITED = 3; 39 | 40 | const DEFAULT_FIRST_PERIOD_SHORTEST_PATH = 10; 41 | const DEFAULT_INCREMENT_SHORTEST_PATH = 5; 42 | 43 | const DEFAULT_SQUARE_SIZE = 30; 44 | 45 | const HEIGHT_THRESHOLD = 910; 46 | 47 | const SPEED_MAPPING = { 48 | 'Low Speed': 10, 49 | 'Medium Speed': 4, 50 | 'High Speed': 0.75, 51 | }; 52 | 53 | const mapDispatchToProps = { 54 | initializeGraph: initGraph, 55 | delNode: deleteNode, 56 | insertNode: addNode, 57 | addWeighted: addWeightedNode, 58 | changeSorce: changeSourceNode, 59 | changeDestination: changeDestinationNode, 60 | setTable: changeTable, 61 | setRunning: changeRunningState, 62 | clearAppState: clearApp, 63 | clearGraphState: clearGraph, 64 | setSelectedAlg: changeAlgorithm, 65 | setSpeed: changeSpeed, 66 | resetGraphForAlg: resetGraphForNewAlgorithm, 67 | setTimeouts: setUiActions, 68 | }; 69 | 70 | const mapStateToProps = (state: AlgorithmVisualizerState) => ({ 71 | source: state.graph.source, 72 | destination: state.graph.destination, 73 | selectedAlg: state.app.selectedAlg, 74 | graphState: state.graph, 75 | table: state.graph.table, 76 | running: state.app.running, 77 | timeOuts: state.app.uiActions, 78 | speed: state.app.speed, 79 | }); 80 | 81 | const connector = connect(mapStateToProps, mapDispatchToProps); 82 | type GraphContainerAlgorithmsProps = ConnectedProps; 83 | 84 | const GraphContainerAlgorithms = (props: GraphContainerAlgorithmsProps): JSX.Element => { 85 | const { 86 | running, 87 | table, 88 | setTable, 89 | setRunning, 90 | selectedAlg, 91 | setTimeouts, 92 | graphState, 93 | initializeGraph, 94 | clearAppState, 95 | clearGraphState, 96 | setSelectedAlg, 97 | setSpeed, 98 | resetGraphForAlg, 99 | delNode, 100 | insertNode, 101 | addWeighted, 102 | changeDestination, 103 | changeSorce, 104 | speed, 105 | } = props; 106 | const [width, height] = useWindowSizeDivided(DEFAULT_SQUARE_SIZE, DEFAULT_SQUARE_SIZE); 107 | const [, realHeight] = useWindowSize(); 108 | const [activeNodeType, setActiveNodeType] = React.useState(RESTORE_NODE_BUTTON as NodeTypeButtonType); 109 | const [stillRunning, setStillRunning] = React.useState(false); 110 | const tableRef = React.useRef(props.table); 111 | const timeOutsRef = React.useRef(props.timeOuts); 112 | tableRef.current = props.table; 113 | timeOutsRef.current = props.timeOuts; 114 | 115 | const speedMultiplier = SPEED_MAPPING[speed]; 116 | 117 | const handleShowShortestPath = React.useCallback( 118 | (shortestPath: Pair[]) => { 119 | let currentDelay = DEFAULT_FIRST_PERIOD_SHORTEST_PATH; 120 | const timeOuts: ReturnType[] = []; 121 | 122 | shortestPath.forEach((pair: Pair) => { 123 | timeOuts.push( 124 | setTimeout(() => { 125 | const { row, col, weight } = pair; 126 | const newTable = copyTableImmutable(tableRef.current); 127 | if (newTable[row][col].nodeType === VISITED_WEIGHT_NODE) { 128 | newTable[row][col] = { nodeType: VISITED_WEIGHT_SHORTEST_PATH_NODE, weight: weight }; 129 | } else { 130 | newTable[row][col] = { nodeType: SHORTEST_PATH_NODE, weight: weight }; 131 | } 132 | 133 | setTable(newTable); 134 | }, currentDelay * speedMultiplier), 135 | ); 136 | currentDelay += DEFAULT_INCREMENT_SHORTEST_PATH; 137 | }); 138 | timeOuts.push( 139 | setTimeout(() => { 140 | setRunning(false); 141 | setStillRunning(false); 142 | }, currentDelay * speedMultiplier), 143 | ); 144 | 145 | return timeOuts; 146 | }, 147 | [speedMultiplier, setRunning, setTable], 148 | ); 149 | 150 | const handleAlgorithmStartsRunning = React.useCallback((): void => { 151 | if (algorithmDoesNoatAcceptWeights(table, selectedAlg)) { 152 | createErrorToast(`You cannot run ${selectedAlg} with weights`); 153 | setStillRunning(false); 154 | setRunning(false); 155 | return; 156 | } 157 | 158 | const { visitedNodesInOrder, shortestPath }: GraphAlgorithmResult = getVisitedNodes(selectedAlg, graphState); 159 | 160 | const timeOuts: ReturnType[] = []; 161 | 162 | let currentSetTimeOutDelay = DEFAULT_FIRST_PERIOD_VISITED; 163 | visitedNodesInOrder.forEach((pair: Pair, index: number) => { 164 | timeOuts.push( 165 | setTimeout(() => { 166 | const { row, col, weight } = pair; 167 | const newTable = copyTableImmutable(tableRef.current); 168 | if (newTable[row][col].nodeType === WEIGHTED_NODE) { 169 | newTable[row][col] = { nodeType: VISITED_WEIGHT_NODE, weight: weight }; 170 | } else { 171 | newTable[row][col] = { nodeType: VISITED_NODE, weight: weight }; 172 | } 173 | 174 | setTable(newTable); 175 | if (index === visitedNodesInOrder.length - 1) { 176 | timeOuts.concat(handleShowShortestPath(shortestPath)); 177 | } 178 | }, currentSetTimeOutDelay * speedMultiplier), 179 | ); 180 | currentSetTimeOutDelay += DEFAULT_INCREMENT_VISITED; 181 | }); 182 | setTimeouts(timeOuts); 183 | }, [setTimeouts, setTable, speedMultiplier, table, selectedAlg, setRunning, handleShowShortestPath, graphState]); 184 | 185 | const clearApp = React.useCallback(() => { 186 | clearAppState(); 187 | clearGraphState(); 188 | }, [clearAppState, clearGraphState]); 189 | 190 | React.useEffect(() => { 191 | let heightCopy = height; 192 | const widthCopy = width; 193 | if (realHeight < HEIGHT_THRESHOLD) { 194 | heightCopy = (0.9 * heightCopy) | 0; 195 | } 196 | initializeGraph(heightCopy, widthCopy); 197 | }, [height, width, realHeight, initializeGraph]); 198 | 199 | React.useEffect(() => { 200 | if (running && !stillRunning && isGraphAlgorithm(selectedAlg)) { 201 | setStillRunning(true); 202 | handleAlgorithmStartsRunning(); 203 | } 204 | if (!running && timeOutsRef.current.length > 0) { 205 | timeOutsRef.current.forEach((timeout) => clearTimeout(timeout)); 206 | setStillRunning(false); 207 | setTimeouts([]); 208 | } 209 | }, [running, stillRunning, selectedAlg, handleAlgorithmStartsRunning, setTimeouts]); 210 | 211 | return ( 212 | <> 213 | >} 222 | setSpeed={setSpeed} 223 | resetGraphForAlg={resetGraphForAlg} 224 | /> 225 |
226 | >} 232 | selectedAlg={selectedAlg} 233 | setGraph={setTable} 234 | changeSourceNode={changeSorce} 235 | changeDestinationNode={changeDestination} 236 | deleteNode={delNode} 237 | addNode={insertNode} 238 | addWeightedNode={addWeighted} 239 | running={running} 240 | graphState={graphState} 241 | /> 242 |
243 | 244 | ); 245 | }; 246 | 247 | export default React.memo(connector(GraphContainerAlgorithms)); 248 | -------------------------------------------------------------------------------- /src/containers/HomePage/AlgorithmsSection/AlgorithmClassCard/AlgorithmClassCard.module.css: -------------------------------------------------------------------------------- 1 | .algorithmTitle { 2 | color: #fff; 3 | font-size: 5em; 4 | font-weight: 900; 5 | } 6 | 7 | .algorithmText { 8 | color: rgba(255, 255, 255, 0.7); 9 | font-size: 1.8em; 10 | font-weight: 100; 11 | margin: -10px 30px 10px; 12 | } 13 | 14 | .algorithmText:hover { 15 | cursor: pointer; 16 | } 17 | 18 | .algorithmTitle:hover { 19 | cursor: pointer; 20 | } 21 | 22 | .pane:hover { 23 | cursor: pointer; 24 | } 25 | -------------------------------------------------------------------------------- /src/containers/HomePage/AlgorithmsSection/AlgorithmClassCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Pane } from 'evergreen-ui'; 3 | import classes from './AlgorithmClassCard.module.css'; 4 | import { RouteComponentProps } from 'react-router-dom'; 5 | import { withRouter } from 'react-router-dom'; 6 | 7 | export type AlgorithmClassCardProps = Record; 8 | 9 | export type AlgorithmClassCardRouterProps = RouteComponentProps & { 10 | color: string; 11 | algTitle: string; 12 | algText: string; 13 | linkTo: string; 14 | textColor: string; 15 | }; 16 | 17 | const AlgorithmClassCard = withRouter( 18 | (props: AlgorithmClassCardRouterProps): JSX.Element => { 19 | return ( 20 | props.history.push(props.linkTo)} 34 | > 35 |

36 | {props.algTitle} 37 |

38 |

39 | {props.algText} 40 |

41 |
42 | ); 43 | }, 44 | ); 45 | 46 | export default AlgorithmClassCard; 47 | -------------------------------------------------------------------------------- /src/containers/HomePage/AlgorithmsSection/AlgorithmsSection.module.css: -------------------------------------------------------------------------------- 1 | .algorithmsSection { 2 | display: table; 3 | height: 976px; 4 | width: 100%; 5 | table-layout: fixed; 6 | text-align: center; 7 | position: relative; 8 | } 9 | -------------------------------------------------------------------------------- /src/containers/HomePage/AlgorithmsSection/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classes from './AlgorithmsSection.module.css'; 3 | import AlgorithmClassCard from './AlgorithmClassCard'; 4 | 5 | const algorithmTexts = [ 6 | { 7 | color: '#306BAC', 8 | algTitle: 'Graph Algorithms', 9 | algText: 'Explore the main pathfinding algorithms', 10 | linkTo: '/graphs', 11 | textColor: '#fff', 12 | }, 13 | { 14 | color: '#DDC9B4', 15 | algTitle: 'Sorting Algorithms', 16 | algText: 'Explore the main sorting algorithms', 17 | linkTo: '/sorting', 18 | textColor: '#000', 19 | }, 20 | ]; 21 | 22 | const AlgorithmsSection = (): JSX.Element => { 23 | return ( 24 |
25 | {algorithmTexts.map((el) => ( 26 |
27 | 35 |
36 | ))} 37 |
38 | ); 39 | }; 40 | 41 | export default AlgorithmsSection; 42 | -------------------------------------------------------------------------------- /src/containers/HomePage/PresentationSection/PresentationSection.module.css: -------------------------------------------------------------------------------- 1 | .presentationTitle { 2 | color: #fff; 3 | font-size: 10em; 4 | font-weight: 900; 5 | } 6 | 7 | .presentationText { 8 | color: rgba(255, 255, 255, 0.7); 9 | font-size: 1.8em; 10 | font-weight: 100; 11 | margin: -10px 30px 10px; 12 | } 13 | 14 | .presentationSection { 15 | display: table; 16 | height: 976px; 17 | width: 100%; 18 | table-layout: fixed; 19 | text-align: center; 20 | position: relative; 21 | } 22 | -------------------------------------------------------------------------------- /src/containers/HomePage/PresentationSection/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import GithubStarCount from '../../../components/GithubStarCount'; 3 | import classes from './PresentationSection.module.css'; 4 | 5 | const PresentationSection = (): JSX.Element => { 6 | return ( 7 |
8 |

Algorithm Visualizer

9 |

Experience Beautiful Algorithm Visualizations

10 | 11 |
12 | ); 13 | }; 14 | 15 | export default PresentationSection; 16 | -------------------------------------------------------------------------------- /src/containers/HomePage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { withRouter } from 'react-router-dom'; 3 | import ReactFullPage from '@fullpage/react-fullpage'; 4 | import PresentationSection from './PresentationSection'; 5 | import AlgorithmsSection from './AlgorithmsSection'; 6 | 7 | import './overrides.css'; 8 | 9 | const sectionColors: string[] = ['#666A86', '#788AA3', '#92B6B1']; 10 | 11 | const HomePage = withRouter(() => { 12 | return ( 13 | { 18 | return ( 19 |
20 | 21 | 22 | 23 | {/*
24 |

Section 3

25 | 26 |
*/} 27 |
28 | ); 29 | }} 30 | /> 31 | ); 32 | }); 33 | 34 | export default HomePage; 35 | -------------------------------------------------------------------------------- /src/containers/HomePage/overrides.css: -------------------------------------------------------------------------------- 1 | /*Overrides must match specificity of pre-loaded styles!*/ 2 | /*The simplest way to get the selector is with devtools*/ 3 | #fp-nav ul li:hover a.active span, 4 | #fp-nav ul li a.active span, 5 | .fp-slidesNav ul li:hover a.active span, 6 | .fp-slidesNav ul li a.active span { 7 | background-color: #fff; 8 | } 9 | 10 | .fp-controlArrow.fp-prev { 11 | margin-left: 30px; 12 | } 13 | .fp-controlArrow.fp-next { 14 | margin-right: 30px; 15 | } 16 | -------------------------------------------------------------------------------- /src/containers/SortingContainerAlgorithms/SortingContainerAlgorithms.module.css: -------------------------------------------------------------------------------- 1 | .sortingContainerAlgorithms { 2 | align-items: center; 3 | text-align: center; 4 | margin-top: 60px; 5 | background-color: #666a86; 6 | } 7 | -------------------------------------------------------------------------------- /src/containers/SortingContainerAlgorithms/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import { SortingStacks } from '../../components/SortingStacks'; 4 | import SortOptionsButtonGroup from '../../components/SortOptionsButtonGroup'; 5 | import { changeAlgorithm, changeRunningState, changeSpeed, clearApp, setUiActions } from '../../store/app/actions'; 6 | import { 7 | changeHighest, 8 | changeListSize, 9 | changeLowest, 10 | changeSortingList, 11 | initSort, 12 | resetList, 13 | } from '../../store/sorting/actions'; 14 | import { AlgorithmVisualizerState } from '../../store/state'; 15 | import { 16 | copyNumbersImmutable, 17 | getSorginAlgorithmOutput, 18 | isSortingAlgorithm, 19 | } from '../../utils/sorting-utils-functions'; 20 | import { 21 | CURRENT_INDEX, 22 | FINISHED_LEFT_TO_MERGE, 23 | FINISHED_MERGE_PAIR, 24 | FINISHED_VISITING_INDEX, 25 | LEFT_TO_MERGE, 26 | MERGE_PAIR, 27 | PLACED_NUMBER, 28 | SortingOutputElementType, 29 | SWAPPED_PAIR, 30 | SWAP_PAIR, 31 | } from '../../utils/types/sorting-types/sorting-results-types'; 32 | import { 33 | CURRENT_STACK, 34 | LEFT_TO_MERGE_STACK, 35 | MERGING_STACK, 36 | PUT_IN_PLACE, 37 | SWAP_STACK, 38 | UNVISITED_STACK, 39 | } from '../../utils/types/sorting-types/sorting-stack-type'; 40 | import classes from './SortingContainerAlgorithms.module.css'; 41 | 42 | const DEFAULT_MIN = 1; 43 | const DEFAULT_MAX = 50; 44 | 45 | const MIN_ARR_LENGTH = 4; 46 | const MAX_ARR_LENGTH = 90; 47 | 48 | const DEFAULT_FIRST_PERIOD = 50; 49 | const DEFAULT_INCREMENT = 50; 50 | 51 | const SPEED_MAPPING = { 52 | 'Low Speed': 4, 53 | 'Medium Speed': 1, 54 | 'High Speed': 0.2, 55 | }; 56 | 57 | const mapDispatchToProps = { 58 | initializeSort: initSort, 59 | setSelectedAlg: changeAlgorithm, 60 | setSpeed: changeSpeed, 61 | setLowest: changeLowest, 62 | setHighest: changeHighest, 63 | changeList: changeSortingList, 64 | setRunning: changeRunningState, 65 | clearAll: clearApp, 66 | regenList: resetList, 67 | setListSize: changeListSize, 68 | setTimeouts: setUiActions, 69 | }; 70 | 71 | const mapStateToProps = (state: AlgorithmVisualizerState) => ({ 72 | running: state.app.running, 73 | lowest: state.sorting.lowest, 74 | highest: state.sorting.highest, 75 | nums: state.sorting.sortingList.numberList, 76 | selectedAlg: state.app.selectedAlg, 77 | sortingState: state.sorting, 78 | stacksList: state.sorting.sortingList.numberList, 79 | speed: state.app.speed, 80 | listSize: state.sorting.listSize, 81 | timeOuts: state.app.uiActions, 82 | initList: state.sorting.initSortingList, 83 | }); 84 | 85 | const connector = connect(mapStateToProps, mapDispatchToProps); 86 | 87 | type SortingContainerAlgorithmsProps = ConnectedProps; 88 | 89 | const SortingContainerAlgorithms = (props: SortingContainerAlgorithmsProps): JSX.Element => { 90 | const { 91 | running, 92 | setListSize, 93 | setLowest, 94 | setHighest, 95 | lowest, 96 | highest, 97 | listSize, 98 | changeList, 99 | selectedAlg, 100 | sortingState, 101 | setRunning, 102 | setTimeouts, 103 | setSelectedAlg, 104 | setSpeed, 105 | clearAll, 106 | initializeSort, 107 | regenList, 108 | } = props; 109 | const [stillRunning, setStillRunning] = React.useState(false); 110 | const timeOutsRef = React.useRef(props.timeOuts); 111 | const stackListRef = React.useRef(props.stacksList); 112 | stackListRef.current = props.stacksList; 113 | timeOutsRef.current = props.timeOuts; 114 | 115 | const speedMultiplier: number = SPEED_MAPPING[props.speed]; 116 | 117 | const reinitList = (): void => { 118 | props.changeList(props.initList.numberList); 119 | }; 120 | 121 | React.useEffect(() => { 122 | setLowest(DEFAULT_MIN); 123 | setHighest(DEFAULT_MAX); 124 | setListSize(((MIN_ARR_LENGTH + MAX_ARR_LENGTH) / 2) | 0); 125 | }, [setListSize, setHighest, setLowest]); 126 | 127 | React.useEffect(() => { 128 | if (lowest !== 0 && highest !== 0 && highest !== 0 && listSize > 0) { 129 | initializeSort(lowest, highest, listSize); 130 | } 131 | }, [lowest, highest, listSize, initializeSort]); 132 | 133 | const handleAlgorithmStartsRunning = React.useCallback(() => { 134 | const algSortingResult = getSorginAlgorithmOutput(selectedAlg, sortingState); 135 | const timeOuts: ReturnType[] = []; 136 | 137 | let currentTimeoutDelay = DEFAULT_FIRST_PERIOD; 138 | algSortingResult.output.forEach((elem: SortingOutputElementType) => { 139 | timeOuts.push( 140 | setTimeout(() => { 141 | const newArray = copyNumbersImmutable(stackListRef.current); 142 | 143 | if (elem.type === PLACED_NUMBER) { 144 | const index = elem.index; 145 | newArray[index] = { ...newArray[index], elemType: PUT_IN_PLACE }; 146 | } else if (elem.type === SWAP_PAIR) { 147 | const { firstIndex, secondIndex } = elem; 148 | newArray[firstIndex] = { ...newArray[firstIndex], elemType: SWAP_STACK }; 149 | newArray[secondIndex] = { ...newArray[secondIndex], elemType: SWAP_STACK }; 150 | const tmp = newArray[firstIndex]; 151 | newArray[firstIndex] = newArray[secondIndex]; 152 | newArray[secondIndex] = tmp; 153 | } else if (elem.type === CURRENT_INDEX) { 154 | const index = elem.index; 155 | newArray[index] = { ...newArray[index], elemType: CURRENT_STACK }; 156 | } else if (elem.type === SWAPPED_PAIR) { 157 | const { firstIndex, secondIndex } = elem; 158 | newArray[firstIndex] = { ...newArray[firstIndex], elemType: UNVISITED_STACK }; 159 | newArray[secondIndex] = { ...newArray[secondIndex], elemType: UNVISITED_STACK }; 160 | } else if (elem.type === FINISHED_VISITING_INDEX) { 161 | const index = elem.index; 162 | newArray[index] = { ...newArray[index], elemType: UNVISITED_STACK }; 163 | } else if (elem.type === MERGE_PAIR) { 164 | const { firstIndex, secondIndex } = elem; 165 | newArray[firstIndex] = { ...newArray[firstIndex], elemType: MERGING_STACK }; 166 | newArray[secondIndex] = { ...newArray[secondIndex], elemType: MERGING_STACK }; 167 | } else if (elem.type === FINISHED_MERGE_PAIR) { 168 | const { firstIndex, secondIndex, currentIndex, placedNumber } = elem; 169 | newArray[firstIndex] = { ...newArray[firstIndex], elemType: UNVISITED_STACK }; 170 | newArray[secondIndex] = { ...newArray[secondIndex], elemType: UNVISITED_STACK }; 171 | newArray[currentIndex] = { ...newArray[currentIndex], number: placedNumber }; 172 | } else if (elem.type === LEFT_TO_MERGE) { 173 | const index = elem.indexLeftToMerge; 174 | newArray[index] = { ...newArray[index], elemType: LEFT_TO_MERGE_STACK }; 175 | } else if (elem.type === FINISHED_LEFT_TO_MERGE) { 176 | const { indexLeftToMerge, currentIndex, placedNumber } = elem; 177 | newArray[indexLeftToMerge] = { ...newArray[indexLeftToMerge], elemType: UNVISITED_STACK }; 178 | newArray[currentIndex] = { ...newArray[currentIndex], number: placedNumber }; 179 | } 180 | 181 | changeList(newArray); 182 | }, currentTimeoutDelay * speedMultiplier), 183 | ); 184 | currentTimeoutDelay += DEFAULT_INCREMENT; 185 | }); 186 | timeOuts.push( 187 | setTimeout(() => { 188 | setRunning(false); 189 | setStillRunning(false); 190 | }, currentTimeoutDelay * speedMultiplier), 191 | ); 192 | setTimeouts(timeOuts); 193 | }, [selectedAlg, sortingState, changeList, setRunning, setTimeouts, speedMultiplier]); 194 | 195 | React.useEffect(() => { 196 | if (running && !stillRunning && isSortingAlgorithm(selectedAlg)) { 197 | setStillRunning(true); 198 | handleAlgorithmStartsRunning(); 199 | } 200 | 201 | if (!running && timeOutsRef.current.length > 0) { 202 | timeOutsRef.current.forEach((timeout) => clearTimeout(timeout)); 203 | setStillRunning(false); 204 | setTimeouts([]); 205 | } 206 | }, [running, stillRunning, selectedAlg, setTimeouts, handleAlgorithmStartsRunning]); 207 | 208 | return ( 209 | <> 210 | 223 |
224 | 225 |
226 | 227 | ); 228 | }; 229 | 230 | export default connector(SortingContainerAlgorithms); 231 | -------------------------------------------------------------------------------- /src/hoc/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect, ConnectedProps } from 'react-redux'; 3 | import Toolbar from '../../components/Navigation/Toolbar'; 4 | import { clearApp } from '../../store/app/actions'; 5 | import { clearGraph } from '../../store/graph/actions'; 6 | import { resetList } from '../../store/sorting/actions'; 7 | 8 | export type InitialLayoutProps = { 9 | children: React.ReactNode[] | React.ReactNode; 10 | }; 11 | 12 | const mapDispatchToProps = { 13 | clearApp: clearApp, 14 | clearGraph: clearGraph, 15 | clearList: resetList, 16 | }; 17 | 18 | const connector = connect(null, mapDispatchToProps); 19 | 20 | type LayoutProps = ConnectedProps & InitialLayoutProps; 21 | 22 | const Layout = (props: LayoutProps): JSX.Element => { 23 | const clearApp = () => { 24 | // props.clearGraph(); 25 | props.clearList(); 26 | props.clearApp(); 27 | }; 28 | 29 | return ( 30 | <> 31 | 32 |
{props.children}
33 | 34 | ); 35 | }; 36 | 37 | export default connector(Layout); 38 | -------------------------------------------------------------------------------- /src/hooks/hooks.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const useWindowSizeDivided = (widthDivider: number, heightDivider: number): number[] => { 4 | const [size, setSize] = React.useState([ 5 | Math.round((window.innerWidth - 0.12 * window.innerWidth) / widthDivider), 6 | Math.round((window.innerHeight - 0.32 * window.innerHeight) / heightDivider), 7 | ]); 8 | React.useLayoutEffect(() => { 9 | function updateSize() { 10 | setSize([ 11 | Math.round((window.innerWidth - 0.12 * window.innerWidth) / widthDivider), 12 | Math.round((window.innerHeight - 0.32 * window.innerHeight) / heightDivider), 13 | ]); 14 | } 15 | window.addEventListener('resize', updateSize); 16 | updateSize(); 17 | return () => window.removeEventListener('resize', updateSize); 18 | }, [heightDivider, widthDivider]); 19 | return size; 20 | }; 21 | 22 | export const useWindowSize = (): number[] => { 23 | const [size, setSize] = React.useState([window.innerWidth, window.innerHeight]); 24 | React.useLayoutEffect(() => { 25 | function updateSize() { 26 | setSize([window.innerWidth, window.innerHeight]); 27 | } 28 | window.addEventListener('resize', updateSize); 29 | updateSize(); 30 | return () => window.removeEventListener('resize', updateSize); 31 | }, []); 32 | return size; 33 | }; 34 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: arial, helvetica; 5 | background: #666a86 !important; 6 | overflow: hidden !important; 7 | } 8 | 9 | *, 10 | *::after, 11 | *::before { 12 | -webkit-user-select: none; 13 | -webkit-user-drag: none; 14 | -webkit-app-region: no-drag; 15 | cursor: default; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import { HashRouter as Router } from 'react-router-dom'; 7 | import { rootReducer } from './store/index'; 8 | import { createStore, applyMiddleware } from 'redux'; 9 | import { createLogger } from 'redux-logger'; 10 | import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css'; 11 | import 'rc-tooltip/assets/bootstrap.css'; 12 | import 'bootstrap/dist/css/bootstrap.min.css'; 13 | 14 | const store = createStore(rootReducer, applyMiddleware(createLogger())); 15 | 16 | ReactDOM.render( 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById('root'), 23 | ); 24 | 25 | serviceWorker.unregister(); 26 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/react-fullpage.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@fullpage/react-fullpage' { 2 | export interface fullpageOptions { 3 | anchors?: string[]; 4 | animateAnchor?: boolean; 5 | autoScrolling?: boolean; 6 | bigSectionsDestination?: any; //top, bottom, null 7 | continuousHorizontal?: boolean; 8 | continuousVertical?: boolean; 9 | controlArrowColor?: string; 10 | controlArrows?: boolean; 11 | css3?: boolean; 12 | dragAndMove?: any; //true, false, 'horizontal', 'fingersonly' 13 | easing?: string; 14 | easingcss3?: string; 15 | fadingEffect?: any; //true, false, 'sections', 'slides' 16 | fitToSection?: boolean; 17 | fitToSectionDelay?: number; 18 | fixedElements?: string; // '#header, .footer' 19 | hybrid?: boolean; 20 | interlockedSlides?: any; // true, false, [1, 3, 5] 21 | keyboardScrolling?: boolean; 22 | lazyLoading?: boolean; 23 | licenseKey?: string; 24 | lockAnchors?: boolean; 25 | loopBottom?: boolean; 26 | loopHorizontal?: boolean; 27 | loopTop?: boolean; 28 | menu?: string; 29 | navigation?: boolean; 30 | navigationPosition?: string; 31 | navigationTooltips?: string[]; // ['firstSlide', 'secondSlide'] 32 | normalScrollElementTouchThreshold?: number; 33 | normalScrollElements?: string; // '#element1, .element2', 34 | offsetSections?: boolean; 35 | paddingBottom?: string; 36 | paddingTop?: string; 37 | parallax?: any; // true, false, 'sections', 'slides' 38 | parallaxOptions?: { 39 | percentage?: number; 40 | property?: string; 41 | type?: string; 42 | }; 43 | recordHistory?: boolean; 44 | resetSliders?: boolean; 45 | responsive?: number; 46 | responsiveHeight?: number; 47 | responsiveSlides?: boolean; 48 | responsiveWidth?: number; 49 | scrollBar?: boolean; 50 | scrollHorizontally?: boolean; 51 | scrollOverflow?: boolean; 52 | scrollOverflowHandler?: any; 53 | scrollOverflowOptions?: any; 54 | scrollOverflowReset?: boolean; 55 | scrollingSpeed?: number; 56 | sectionSelector?: string; 57 | sectionsColor?: string[]; 58 | showActiveTooltip?: boolean; 59 | slideSelector?: string; 60 | slidesNavPosition?: string; 61 | slidesNavigation?: boolean; 62 | touchSensitivity?: number; 63 | v2compatible?: boolean; 64 | verticalCentered?: boolean; 65 | 66 | /* callback and events */ 67 | afterLoad?(origin?: any, destination?: any, direction?: any): void; 68 | afterRender?(): void; 69 | afterResize?(width: number, height: number): void; 70 | afterReBuild?(): void; 71 | afterResponsive?(isResponsive?: any): void; 72 | afterSlideLoad?(section?: any, origin?: any, destination?: any, direction?: any): void; 73 | onLeave?(origin?: any, destination?: any, direction?: any): void; 74 | onSlideLeave?(section?: any, origin?: any, destination?: any, direction?: any): void; 75 | 76 | /* keys for extensions */ 77 | fadingEffectKey?: string; 78 | responsiveSlidesKey?: string; 79 | continuousHorizontalKey?: string; 80 | interlockedSlidesKey?: string; 81 | scrollHorizontallyKey?: string; 82 | resetSlidersKey?: string; 83 | offsetSectionsKey?: string; 84 | dragAndMoveKey?: string; 85 | parallaxKey?: string; 86 | /* end key sections */ 87 | } 88 | 89 | export interface fullpageApi { 90 | continuousHorizontal: any; 91 | dragAndMove: any; 92 | fadingEffect: any; 93 | interlockedSlides: any; 94 | offsetSections: any; 95 | parallax: any; 96 | resetSliders: any; 97 | responsiveSlides: any; 98 | scrollHorizontally: any; 99 | scrollOverflowReset: any; 100 | version: string; 101 | destroy(n?: any): void; 102 | fitToSection(): void; 103 | getActiveSection(): any; 104 | getActiveSlide(): any; 105 | getFullpageData(): any; 106 | landscapeScroll(e?: any, t?: any, n?: any): void; 107 | moveSectionDown(): void; 108 | moveSectionUp(): void; 109 | moveSlideLeft(e?: any): void; 110 | moveSlideRight(e?: any): void; 111 | moveTo(e?: any, t?: any): void; 112 | reBuild(t?: any): void; 113 | setAllowScrolling(e?: any, t?: any): void; 114 | setAutoScrolling(e?: any, t?: any): void; 115 | setFitToSection(e?: any, t?: any): void; 116 | setKeyboardScrolling(e?: any, t?: any): void; 117 | setLockAnchors(e?: any): void; 118 | setMouseWheelScrolling(n?: any): void; 119 | setRecordHistory(e?: any, t?: any): void; 120 | setResponsive(e?: any): void; 121 | setScrollingSpeed(e?: any, t?: any): void; 122 | silentMoveTo(e?: any, t?: any): void; 123 | shared: { 124 | afterRenderActions(): void; 125 | }; 126 | test: { 127 | left: number[]; 128 | top: string; 129 | translate3d: string; 130 | translate3dH: string[]; 131 | setAutoScrolling(e?: any, t?: any): void; 132 | options: fullpageOptions; 133 | }; 134 | } 135 | 136 | interface FullPageProps extends fullpageOptions { 137 | licenseKey?: string; 138 | render: (comp: { state: any; fullpageApi: fullpageApi }) => React.ReactElement | void; 139 | debug?: boolean; 140 | pluginWrapper?: () => void; 141 | } 142 | 143 | class ReactFullpage extends React.Component {} 144 | 145 | interface WrapperProps { 146 | children: React.ReactNode; 147 | } 148 | 149 | namespace ReactFullpage { 150 | function Wrapper(props: WrapperProps): React.ReactElement; 151 | } 152 | 153 | export default ReactFullpage; 154 | } 155 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/), 19 | ); 20 | 21 | type Config = { 22 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 23 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 24 | }; 25 | 26 | export function register(config?: Config) { 27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 28 | // The URL constructor is available in all browsers that support SW. 29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 30 | if (publicUrl.origin !== window.location.origin) { 31 | // Our service worker won't work if PUBLIC_URL is on a different origin 32 | // from what our page is served on. This might happen if a CDN is used to 33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 34 | return; 35 | } 36 | 37 | window.addEventListener('load', () => { 38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 39 | 40 | if (isLocalhost) { 41 | // This is running on localhost. Let's check if a service worker still exists or not. 42 | checkValidServiceWorker(swUrl, config); 43 | 44 | // Add some additional logging to localhost, pointing developers to the 45 | // service worker/PWA documentation. 46 | navigator.serviceWorker.ready.then(() => { 47 | console.log( 48 | 'This web app is being served cache-first by a service ' + 49 | 'worker. To learn more, visit https://bit.ly/CRA-PWA', 50 | ); 51 | }); 52 | } else { 53 | // Is not localhost. Just register service worker 54 | registerValidSW(swUrl, config); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | function registerValidSW(swUrl: string, config?: Config) { 61 | navigator.serviceWorker 62 | .register(swUrl) 63 | .then((registration) => { 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing; 66 | if (installingWorker == null) { 67 | return; 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.', 78 | ); 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration); 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.'); 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration); 93 | } 94 | } 95 | } 96 | }; 97 | }; 98 | }) 99 | .catch((error) => { 100 | console.error('Error during service worker registration:', error); 101 | }); 102 | } 103 | 104 | function checkValidServiceWorker(swUrl: string, config?: Config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl, { 107 | headers: { 'Service-Worker': 'script' }, 108 | }) 109 | .then((response) => { 110 | // Ensure service worker exists, and that we really are getting a JS file. 111 | const contentType = response.headers.get('content-type'); 112 | if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then((registration) => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log('No internet connection found. App is running in offline mode.'); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready 132 | .then((registration) => { 133 | registration.unregister(); 134 | }) 135 | .catch((error) => { 136 | console.error(error.message); 137 | }); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store/app/actions.ts: -------------------------------------------------------------------------------- 1 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 2 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 3 | import { 4 | AppActionTypes, 5 | CHANGE_ALGORITHM, 6 | CHANGE_RUNNING_STATE, 7 | CHANGE_SPEED, 8 | CLEAR_APP, 9 | SET_UI_ACTIONS, 10 | } from './types'; 11 | 12 | export const changeAlgorithm = (alg: AlgorithmType): AppActionTypes => ({ 13 | type: CHANGE_ALGORITHM, 14 | algorithm: alg, 15 | }); 16 | 17 | export const changeRunningState = (state: boolean): AppActionTypes => ({ 18 | type: CHANGE_RUNNING_STATE, 19 | state: state, 20 | }); 21 | 22 | export const clearApp = (): AppActionTypes => ({ 23 | type: CLEAR_APP, 24 | }); 25 | 26 | export const changeSpeed = (newSpeed: SpeedType): AppActionTypes => ({ 27 | type: CHANGE_SPEED, 28 | speed: newSpeed, 29 | }); 30 | 31 | export const setUiActions = (newUiActions: ReturnType[]): AppActionTypes => ({ 32 | type: SET_UI_ACTIONS, 33 | newUiActions: newUiActions, 34 | }); 35 | -------------------------------------------------------------------------------- /src/store/app/reducer.ts: -------------------------------------------------------------------------------- 1 | import { MEDIUM_SPEED } from '../../utils/types/app-types/alg-speed-type'; 2 | import { NO_ALGORITHM } from '../../utils/types/graph-types/graph-algorithm-types'; 3 | import { AppState } from './state'; 4 | import { 5 | AppActionTypes, 6 | CHANGE_ALGORITHM, 7 | CHANGE_RUNNING_STATE, 8 | CHANGE_SPEED, 9 | CLEAR_APP, 10 | SET_UI_ACTIONS, 11 | } from './types'; 12 | 13 | export const initialAppState: AppState = { 14 | selectedAlg: NO_ALGORITHM, 15 | running: false, 16 | speed: MEDIUM_SPEED, 17 | uiActions: [], 18 | }; 19 | 20 | export const appReducer = (state = initialAppState, action: AppActionTypes): AppState => { 21 | switch (action.type) { 22 | case CHANGE_ALGORITHM: 23 | return { 24 | selectedAlg: action.algorithm, 25 | running: false, 26 | speed: state.speed, 27 | uiActions: state.uiActions, 28 | }; 29 | case CHANGE_RUNNING_STATE: 30 | return { 31 | selectedAlg: state.selectedAlg, 32 | running: action.state, 33 | speed: state.speed, 34 | uiActions: state.uiActions, 35 | }; 36 | case CLEAR_APP: 37 | return { 38 | ...state, 39 | running: false, 40 | }; 41 | case CHANGE_SPEED: 42 | return { 43 | ...state, 44 | speed: action.speed, 45 | }; 46 | case SET_UI_ACTIONS: 47 | return { 48 | ...state, 49 | uiActions: action.newUiActions, 50 | }; 51 | default: 52 | return state; 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/store/app/state.ts: -------------------------------------------------------------------------------- 1 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 2 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 3 | 4 | export interface AppState { 5 | selectedAlg: AlgorithmType; 6 | running: boolean; 7 | speed: SpeedType; 8 | uiActions: ReturnType[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/store/app/types.ts: -------------------------------------------------------------------------------- 1 | import { SpeedType } from '../../utils/types/app-types/alg-speed-type'; 2 | import { AlgorithmType } from '../../utils/types/app-types/algorithm-classes-types'; 3 | 4 | export const CHANGE_ALGORITHM = 'CHANGE_ALGORITHM'; 5 | export const CHANGE_RUNNING_STATE = 'CHANGE_RUNNINT_STATE'; 6 | export const CLEAR_APP = 'CLEAR_APP'; 7 | export const CHANGE_SPEED = 'CHANGE_SPEED'; 8 | export const SET_UI_ACTIONS = 'SET_UI_ACTIONS'; 9 | 10 | interface ChangeAlgorithmAction { 11 | type: typeof CHANGE_ALGORITHM; 12 | algorithm: AlgorithmType; 13 | } 14 | 15 | interface ChangeRunningStateAction { 16 | type: typeof CHANGE_RUNNING_STATE; 17 | state: boolean; 18 | } 19 | 20 | interface ClearAppAction { 21 | type: typeof CLEAR_APP; 22 | } 23 | 24 | interface ChangeSpeedAction { 25 | type: typeof CHANGE_SPEED; 26 | speed: SpeedType; 27 | } 28 | 29 | interface SetUiActionsAction { 30 | type: typeof SET_UI_ACTIONS; 31 | newUiActions: ReturnType[]; 32 | } 33 | 34 | export type AppActionTypes = 35 | | ChangeAlgorithmAction 36 | | ChangeRunningStateAction 37 | | ClearAppAction 38 | | ChangeSpeedAction 39 | | SetUiActionsAction; 40 | -------------------------------------------------------------------------------- /src/store/graph/actions.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../algorithms/graph-algorithms/graph'; 2 | import { TableNodeType } from '../../utils/types/graph-types/table-node-type'; 3 | import { 4 | GraphActionTypes, 5 | ADD_NODE, 6 | DELETE_NODE, 7 | INIT_GRAPH, 8 | CHANGE_SOURCE_NODE, 9 | CHANGE_DESTINATION_NODE, 10 | ADD_WEIGHTED_NODE, 11 | CLEAR_GRAPH, 12 | RESET_GRAPH_FOR_NEW_ALGORITHM, 13 | CHANGE_TABLE, 14 | } from './types'; 15 | 16 | export const addNode = (node: GraphNode, table: TableNodeType[][]): GraphActionTypes => ({ 17 | type: ADD_NODE, 18 | node: node, 19 | table: table, 20 | }); 21 | 22 | export const addWeightedNode = (node: GraphNode, table: TableNodeType[][]): GraphActionTypes => ({ 23 | type: ADD_WEIGHTED_NODE, 24 | node: node, 25 | table: table, 26 | }); 27 | 28 | export const deleteNode = (node: GraphNode): GraphActionTypes => ({ 29 | type: DELETE_NODE, 30 | node: node, 31 | }); 32 | 33 | export const initGraph = (height: number, width: number): GraphActionTypes => ({ 34 | type: INIT_GRAPH, 35 | height: height, 36 | width: width, 37 | }); 38 | 39 | export const changeSourceNode = (newSource: GraphNode): GraphActionTypes => ({ 40 | type: CHANGE_SOURCE_NODE, 41 | newSource: newSource, 42 | }); 43 | 44 | export const changeDestinationNode = (newDest: GraphNode): GraphActionTypes => ({ 45 | type: CHANGE_DESTINATION_NODE, 46 | newDest: newDest, 47 | }); 48 | 49 | export const clearGraph = (): GraphActionTypes => ({ 50 | type: CLEAR_GRAPH, 51 | }); 52 | 53 | export const changeTable = (newTable: TableNodeType[][]): GraphActionTypes => ({ 54 | type: CHANGE_TABLE, 55 | table: newTable, 56 | }); 57 | 58 | export const resetGraphForNewAlgorithm = (): GraphActionTypes => ({ 59 | type: RESET_GRAPH_FOR_NEW_ALGORITHM, 60 | }); 61 | -------------------------------------------------------------------------------- /src/store/graph/reducer.ts: -------------------------------------------------------------------------------- 1 | import { Edges, GraphNode } from '../../algorithms/graph-algorithms/graph'; 2 | import { generateRandomNumber } from '../../utils/app-utils-functions'; 3 | import { 4 | DESTINATION_NODE, 5 | SHORTEST_PATH_NODE, 6 | SIMPLE_NODE, 7 | SOURCE_NODE, 8 | VISITED_NODE, 9 | VISITED_WEIGHT_NODE, 10 | VISITED_WEIGHT_SHORTEST_PATH_NODE, 11 | WEIGHTED_NODE, 12 | } from '../../utils/types/graph-types/node-type'; 13 | import { 14 | copyTableImmutable, 15 | fromIndexToPair, 16 | fromPairToIndex, 17 | getWeightFromNode, 18 | isBlockedNode, 19 | validCoords, 20 | } from '../../utils/graph-utils-functions'; 21 | import { GraphState } from './state'; 22 | 23 | import { 24 | ADD_NODE, 25 | ADD_WEIGHTED_NODE, 26 | CHANGE_DESTINATION_NODE, 27 | CHANGE_SOURCE_NODE, 28 | CHANGE_TABLE, 29 | CLEAR_GRAPH, 30 | DELETE_NODE, 31 | GraphActionTypes, 32 | INIT_GRAPH, 33 | RESET_GRAPH_FOR_NEW_ALGORITHM, 34 | } from './types'; 35 | import { TableNodeType } from '../../utils/types/graph-types/table-node-type'; 36 | 37 | export const initialGraphState: GraphState = { 38 | numberOfNodes: 0, 39 | nodes: [], 40 | edges: {}, 41 | source: null, 42 | destination: null, 43 | height: 0, 44 | width: 0, 45 | table: [[]] as TableNodeType[][], 46 | initializedtable: false, 47 | }; 48 | 49 | const initData = (height: number, width: number): TableNodeType[][] => { 50 | return Array(height) 51 | .fill(null) 52 | .map(() => Array(width).fill({ nodeType: SIMPLE_NODE })); 53 | }; 54 | 55 | const addNodeToGraph = (node: GraphNode, table: TableNodeType[][], state: GraphState): GraphState => { 56 | if (!state.nodes.some((elem: GraphNode) => elem.id === node.id)) { 57 | const newEdges = { ...state.edges }; 58 | 59 | newEdges[node.id] = []; 60 | const { row, col } = fromIndexToPair(parseInt(node.id, 10), state.width); 61 | const dx = [-1, 0, 0, 1]; 62 | const dy = [0, -1, 1, 0]; 63 | 64 | for (let dir = 0; dir < 4; ++dir) { 65 | const adjRow = row + dx[dir]; 66 | const adjCol = col + dy[dir]; 67 | if (validCoords(adjRow, adjCol, state.height, state.width)) { 68 | const adjId = `${fromPairToIndex({ row: adjRow, col: adjCol }, state.width)}`; 69 | const isAdjBlocked = isBlockedNode({ row: adjRow, col: adjCol }, table); 70 | if (!isAdjBlocked && !newEdges[node.id].includes({ id: adjId } as GraphNode)) { 71 | if (table[adjRow][adjCol].nodeType === WEIGHTED_NODE) { 72 | newEdges[node.id].push({ 73 | id: adjId, 74 | weight: getWeightFromNode(adjId, state.nodes), 75 | } as GraphNode); 76 | } else { 77 | newEdges[node.id].push({ 78 | id: adjId, 79 | } as GraphNode); 80 | } 81 | } 82 | 83 | if (!isAdjBlocked && !newEdges[adjId].includes({ id: node.id } as GraphNode)) { 84 | newEdges[adjId].push({ id: node.id, weight: node.weight } as GraphNode); 85 | } 86 | } 87 | } 88 | 89 | return { 90 | numberOfNodes: state.numberOfNodes + 1, 91 | nodes: [...state.nodes, node], 92 | edges: newEdges, 93 | source: state.source, 94 | destination: state.destination, 95 | height: state.height, 96 | width: state.width, 97 | table: copyTableImmutable(state.table), 98 | initializedtable: state.initializedtable, 99 | }; 100 | } 101 | return state; 102 | }; 103 | 104 | const deleteNodeFromGraph = (node: GraphNode, state: GraphState): GraphState => { 105 | if (state.numberOfNodes === 0 || !state.nodes.some((elem: GraphNode) => elem.id === node.id)) { 106 | return state; 107 | } 108 | const newNumofNodes = state.numberOfNodes - 1; 109 | const newNodesSet = state.nodes.filter((n: GraphNode) => n.id !== node.id); 110 | const newEdgeSet = Object.keys(state.edges) 111 | .filter((key: string) => key !== node.id) 112 | .reduce((edges: Edges, key: string) => { 113 | const newAdj = state.edges[key].filter((n: GraphNode) => n.id !== node.id); 114 | edges[key] = newAdj; 115 | return edges; 116 | }, {}); 117 | 118 | return { 119 | numberOfNodes: newNumofNodes, 120 | nodes: newNodesSet, 121 | edges: newEdgeSet, 122 | source: state.source, 123 | destination: state.destination, 124 | height: state.height, 125 | width: state.width, 126 | table: copyTableImmutable(state.table), 127 | initializedtable: state.initializedtable, 128 | }; 129 | }; 130 | 131 | const initGraph = (height: number, width: number): GraphState => { 132 | const numberOfNodes = height * width; 133 | const nodes = Array.from({ length: numberOfNodes }, (_, i) => i).map((elem) => ({ id: `${elem}` } as GraphNode)); 134 | const edges = nodes.reduce((edgeSet: Edges, node: GraphNode) => ({ ...edgeSet, [node.id]: [] }), {} as Edges); 135 | const dx = [-1, 0, 0, 1]; 136 | const dy = [0, -1, 1, 0]; 137 | 138 | for (let node = 0; node < height * width; ++node) { 139 | const { row, col } = fromIndexToPair(node, width); 140 | for (let dir = 0; dir < 4; ++dir) { 141 | const adjRow = row + dx[dir]; 142 | const adjCol = col + dy[dir]; 143 | if (validCoords(adjRow, adjCol, height, width)) { 144 | edges[`${node}`].push({ id: `${fromPairToIndex({ row: adjRow, col: adjCol }, width)}` } as GraphNode); 145 | } 146 | } 147 | } 148 | 149 | const source = { id: `${generateRandomNumber(0, height * width)}` } as GraphNode; 150 | const destination = { id: `${generateRandomNumber(0, height * width)}` } as GraphNode; 151 | 152 | const src = fromIndexToPair(parseInt(source.id, 10), width); 153 | const dst = fromIndexToPair(parseInt(destination.id, 10), width); 154 | const table = initData(height, width); 155 | 156 | table[src.row][src.col] = { nodeType: SOURCE_NODE }; 157 | table[dst.row][dst.col] = { nodeType: DESTINATION_NODE }; 158 | 159 | return { 160 | numberOfNodes: numberOfNodes, 161 | nodes: nodes, 162 | edges: edges, 163 | source: source, 164 | destination: destination, 165 | width: width, 166 | height: height, 167 | table: table, 168 | initializedtable: true, 169 | }; 170 | }; 171 | 172 | const changeSourceNode = (newSource: GraphNode, state: GraphState): GraphState => { 173 | const table = copyTableImmutable(state.table); 174 | const { row, col } = fromIndexToPair(parseInt(state.source?.id || '0', 10), state.width); 175 | table[row][col] = { nodeType: SIMPLE_NODE }; 176 | const p1 = fromIndexToPair(parseInt(newSource.id, 10), state.width); 177 | table[p1.row][p1.col] = { nodeType: SOURCE_NODE }; 178 | return { 179 | ...state, 180 | source: newSource, 181 | }; 182 | }; 183 | 184 | const changeDestinationNode = (newDest: GraphNode, state: GraphState): GraphState => { 185 | const table = copyTableImmutable(state.table); 186 | const { row, col } = fromIndexToPair(parseInt(state.destination?.id || '0', 10), state.width); 187 | table[row][col] = { nodeType: SIMPLE_NODE }; 188 | const p1 = fromIndexToPair(parseInt(newDest.id, 10), state.width); 189 | table[p1.row][p1.col] = { nodeType: DESTINATION_NODE }; 190 | return { 191 | ...state, 192 | destination: newDest, 193 | }; 194 | }; 195 | 196 | const clearGraph = (state: GraphState): GraphState => ({ 197 | ...initGraph(state.height, state.width), 198 | source: state.source, 199 | destination: state.destination, 200 | table: copyTableImmutable(state.table).map((row: TableNodeType[]) => 201 | row.map((elem: TableNodeType) => 202 | elem.nodeType === SOURCE_NODE || elem.nodeType === DESTINATION_NODE ? elem : { nodeType: SIMPLE_NODE }, 203 | ), 204 | ), 205 | }); 206 | 207 | const resetGraphForNewAlgorithm = (state: GraphState): GraphState => { 208 | const newTable = copyTableImmutable(state.table).map((row: TableNodeType[]) => 209 | row.map((elem: TableNodeType) => { 210 | if (elem.nodeType === SHORTEST_PATH_NODE || elem.nodeType === VISITED_NODE) { 211 | return { nodeType: SIMPLE_NODE } as TableNodeType; 212 | } else if (elem.nodeType === VISITED_WEIGHT_NODE || elem.nodeType === VISITED_WEIGHT_SHORTEST_PATH_NODE) { 213 | return { nodeType: WEIGHTED_NODE, weight: elem.weight } as TableNodeType; 214 | } else if (elem.nodeType === WEIGHTED_NODE) { 215 | return elem; 216 | } 217 | 218 | return { nodeType: elem.nodeType } as TableNodeType; 219 | }), 220 | ); 221 | return { 222 | ...state, 223 | table: newTable, 224 | source: state.source, 225 | destination: state.destination, 226 | }; 227 | }; 228 | 229 | const changeTable = (newTable: TableNodeType[][], state: GraphState): GraphState => { 230 | return { 231 | ...state, 232 | table: copyTableImmutable(newTable), 233 | }; 234 | }; 235 | 236 | export const graphReducer = (state = initialGraphState, action: GraphActionTypes): GraphState => { 237 | switch (action.type) { 238 | case ADD_NODE: 239 | return addNodeToGraph(action.node, action.table, state); 240 | case ADD_WEIGHTED_NODE: 241 | return addNodeToGraph(action.node, action.table, deleteNodeFromGraph(action.node, state)); 242 | case DELETE_NODE: 243 | return deleteNodeFromGraph(action.node, state); 244 | case INIT_GRAPH: 245 | return initGraph(action.height, action.width); 246 | case CHANGE_SOURCE_NODE: 247 | return changeSourceNode(action.newSource, state); 248 | case CHANGE_DESTINATION_NODE: 249 | return changeDestinationNode(action.newDest, state); 250 | case CLEAR_GRAPH: 251 | return clearGraph(state); 252 | case CHANGE_TABLE: 253 | return changeTable(action.table, state); 254 | case RESET_GRAPH_FOR_NEW_ALGORITHM: 255 | return resetGraphForNewAlgorithm(state); 256 | default: 257 | return state; 258 | } 259 | }; 260 | -------------------------------------------------------------------------------- /src/store/graph/state.ts: -------------------------------------------------------------------------------- 1 | import { Edges, GraphNode } from '../../algorithms/graph-algorithms/graph'; 2 | import { TableNodeType } from '../../utils/types/graph-types/table-node-type'; 3 | 4 | export interface GraphState { 5 | numberOfNodes: number; 6 | nodes: GraphNode[]; 7 | edges: Edges; 8 | source: GraphNode | null; 9 | destination: GraphNode | null; 10 | height: number; 11 | width: number; 12 | table: TableNodeType[][]; 13 | initializedtable: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/store/graph/types.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../algorithms/graph-algorithms/graph'; 2 | import { TableNodeType } from '../../utils/types/graph-types/table-node-type'; 3 | 4 | export const ADD_NODE = 'ADD_NODE'; 5 | export const ADD_WEIGHTED_NODE = 'ADD_WEIGHTED_NODE'; 6 | export const DELETE_NODE = 'DELETE_NODE'; 7 | export const INIT_GRAPH = 'INIT_GRAPH'; 8 | export const CHANGE_SOURCE_NODE = 'CHANGE_SOURCE_NODE'; 9 | export const CHANGE_DESTINATION_NODE = 'CAHGNE_DESTINATION_NODE'; 10 | export const CLEAR_GRAPH = 'CLEAR_GRAPH'; 11 | export const CHANGE_TABLE = 'CHANGE_TABLE'; 12 | export const RESET_GRAPH_FOR_NEW_ALGORITHM = 'RESET_GRAPH_FOR_NEW_ALGORITHM'; 13 | 14 | interface AddNodeAction { 15 | type: typeof ADD_NODE; 16 | node: GraphNode; 17 | table: TableNodeType[][]; 18 | } 19 | 20 | interface AddWeightedNodeAction { 21 | type: typeof ADD_WEIGHTED_NODE; 22 | node: GraphNode; 23 | table: TableNodeType[][]; 24 | } 25 | 26 | interface DeleteNodeAction { 27 | type: typeof DELETE_NODE; 28 | node: GraphNode; 29 | } 30 | 31 | interface InitGraphAction { 32 | type: typeof INIT_GRAPH; 33 | height: number; 34 | width: number; 35 | } 36 | 37 | interface ChangeSourceNodeAction { 38 | type: typeof CHANGE_SOURCE_NODE; 39 | newSource: GraphNode; 40 | } 41 | 42 | interface ChangeDestinationNodeAction { 43 | type: typeof CHANGE_DESTINATION_NODE; 44 | newDest: GraphNode; 45 | } 46 | 47 | interface ClearGraphAction { 48 | type: typeof CLEAR_GRAPH; 49 | } 50 | 51 | interface ChangeTableAction { 52 | type: typeof CHANGE_TABLE; 53 | table: TableNodeType[][]; 54 | } 55 | 56 | interface ResetGraphForNewAlgorithmAction { 57 | type: typeof RESET_GRAPH_FOR_NEW_ALGORITHM; 58 | } 59 | 60 | export type GraphActionTypes = 61 | | AddNodeAction 62 | | AddWeightedNodeAction 63 | | DeleteNodeAction 64 | | InitGraphAction 65 | | ChangeSourceNodeAction 66 | | ChangeDestinationNodeAction 67 | | ClearGraphAction 68 | | ChangeTableAction 69 | | ResetGraphForNewAlgorithmAction; 70 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { appReducer } from './app/reducer'; 3 | import { graphReducer } from './graph/reducer'; 4 | import { sortingRedcer } from './sorting/reducer'; 5 | 6 | export const rootReducer = combineReducers({ 7 | graph: graphReducer, 8 | sorting: sortingRedcer, 9 | app: appReducer, 10 | }); 11 | 12 | export type RootState = ReturnType; 13 | -------------------------------------------------------------------------------- /src/store/sorting/actions.ts: -------------------------------------------------------------------------------- 1 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 2 | import { 3 | CHANGE_HIGHEST, 4 | CHANGE_LIST_SIZE, 5 | CHANGE_LOWEST, 6 | CHANGE_SORTING_LIST, 7 | CLEAR_SORT, 8 | INIT_SORT, 9 | RESET_LIST, 10 | SortingActions, 11 | } from './types'; 12 | 13 | export const initSort = (lowest: number, highest: number, size: number): SortingActions => ({ 14 | type: INIT_SORT, 15 | lowest: lowest, 16 | highest: highest, 17 | size: size, 18 | }); 19 | 20 | export const changeLowest = (newLowest: number): SortingActions => ({ 21 | type: CHANGE_LOWEST, 22 | newLowest: newLowest, 23 | }); 24 | 25 | export const changeHighest = (newHighest: number): SortingActions => ({ 26 | type: CHANGE_HIGHEST, 27 | newHighest: newHighest, 28 | }); 29 | 30 | export const clearSort = (): SortingActions => ({ 31 | type: CLEAR_SORT, 32 | }); 33 | 34 | export const changeSortingList = (newSortingList: ArrayStackType[]): SortingActions => ({ 35 | type: CHANGE_SORTING_LIST, 36 | newSortingList: newSortingList, 37 | }); 38 | 39 | export const changeListSize = (newSize: number): SortingActions => ({ 40 | type: CHANGE_LIST_SIZE, 41 | newSize: newSize, 42 | }); 43 | 44 | export const resetList = (): SortingActions => ({ 45 | type: RESET_LIST, 46 | }); 47 | -------------------------------------------------------------------------------- /src/store/sorting/reducer.ts: -------------------------------------------------------------------------------- 1 | import { generateRandomNumber } from '../../utils/app-utils-functions'; 2 | import { copyNumbersImmutable } from '../../utils/sorting-utils-functions'; 3 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 4 | import { SortingStackType, UNVISITED_STACK } from '../../utils/types/sorting-types/sorting-stack-type'; 5 | import { SortingState } from './state'; 6 | import { 7 | CHANGE_HIGHEST, 8 | CHANGE_LIST_SIZE, 9 | CHANGE_LOWEST, 10 | CHANGE_SORTING_LIST, 11 | CLEAR_SORT, 12 | INIT_SORT, 13 | RESET_LIST, 14 | SortingActions, 15 | } from './types'; 16 | 17 | const initialSortingState: SortingState = { 18 | sortingList: { numberList: [] }, 19 | initSortingList: { numberList: [] }, 20 | listSize: 0, 21 | lowest: 0, 22 | highest: 0, 23 | }; 24 | 25 | const initSort = (state: SortingState, lowest: number, highest: number, size: number): SortingState => { 26 | if (highest < lowest) { 27 | return state; 28 | } 29 | const numList = Array.from({ length: size }, () => ({ 30 | elemType: UNVISITED_STACK as SortingStackType, 31 | number: generateRandomNumber(lowest, highest + 1), 32 | })); 33 | const newState = { 34 | ...state, 35 | lowest: lowest, 36 | highest: highest, 37 | sortingList: { 38 | numberList: numList, 39 | }, 40 | initSortingList: { 41 | numberList: copyNumbersImmutable(numList), 42 | }, 43 | }; 44 | return newState; 45 | }; 46 | 47 | const changeLowest = (state: SortingState, newLowest: number): SortingState => ({ 48 | ...state, 49 | lowest: newLowest, 50 | }); 51 | 52 | const changeHighest = (state: SortingState, newHighest: number): SortingState => ({ 53 | ...state, 54 | highest: newHighest, 55 | }); 56 | 57 | const clearSort = (state: SortingState): SortingState => ({ 58 | ...initSort(state, state.lowest, state.highest, state.listSize), 59 | }); 60 | 61 | const changeSortingList = (state: SortingState, newSortingList: ArrayStackType[]): SortingState => ({ 62 | ...state, 63 | sortingList: { numberList: copyNumbersImmutable(newSortingList) }, 64 | }); 65 | 66 | const changeListSize = (state: SortingState, newSize: number): SortingState => ({ 67 | ...state, 68 | listSize: newSize, 69 | }); 70 | 71 | const resetList = (state: SortingState): SortingState => ({ 72 | ...initSort(state, state.lowest, state.highest, state.listSize), 73 | }); 74 | 75 | export const sortingRedcer = (state = initialSortingState, action: SortingActions): SortingState => { 76 | switch (action.type) { 77 | case INIT_SORT: 78 | return initSort(state, action.lowest, action.highest, action.size); 79 | case CHANGE_LOWEST: 80 | return changeLowest(state, action.newLowest); 81 | case CHANGE_HIGHEST: 82 | return changeHighest(state, action.newHighest); 83 | case CLEAR_SORT: 84 | return clearSort(state); 85 | case CHANGE_SORTING_LIST: 86 | return changeSortingList(state, action.newSortingList); 87 | case CHANGE_LIST_SIZE: 88 | return changeListSize(state, action.newSize); 89 | case RESET_LIST: 90 | return resetList(state); 91 | default: 92 | return state; 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/store/sorting/state.ts: -------------------------------------------------------------------------------- 1 | import { SortingList } from '../../algorithms/sorting-algorithms/sorting'; 2 | 3 | export const ASCENDING = 'ASCENDING'; 4 | export const DESCENDING = 'DESCENDING'; 5 | 6 | export interface SortingState { 7 | sortingList: SortingList; 8 | initSortingList: SortingList; 9 | listSize: number; 10 | lowest: number; 11 | highest: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/store/sorting/types.ts: -------------------------------------------------------------------------------- 1 | import { ArrayStackType } from '../../utils/types/sorting-types/array-stack-type'; 2 | 3 | export const INIT_SORT = 'INIT_SORT'; 4 | export const CHANGE_LOWEST = 'CHANGE_LOWEST'; 5 | export const CHANGE_HIGHEST = 'CHANGE_HIGHEST'; 6 | export const CLEAR_SORT = 'CLEAR_SORT'; 7 | export const CHANGE_SORTING_LIST = 'CHANGE_SORTING_LIST'; 8 | export const CHANGE_LIST_SIZE = 'CHANGE_LIST_SIZE'; 9 | export const RESET_LIST = 'RESET_LIST'; 10 | 11 | interface InitSortAction { 12 | type: typeof INIT_SORT; 13 | size: number; 14 | lowest: number; 15 | highest: number; 16 | } 17 | 18 | interface ChangeLowestAction { 19 | type: typeof CHANGE_LOWEST; 20 | newLowest: number; 21 | } 22 | 23 | interface ChangeHighestAction { 24 | type: typeof CHANGE_HIGHEST; 25 | newHighest: number; 26 | } 27 | 28 | interface ClearSortAction { 29 | type: typeof CLEAR_SORT; 30 | } 31 | 32 | interface ChangeSortingListAction { 33 | type: typeof CHANGE_SORTING_LIST; 34 | newSortingList: ArrayStackType[]; 35 | } 36 | 37 | interface ChangeListSizeAction { 38 | type: typeof CHANGE_LIST_SIZE; 39 | newSize: number; 40 | } 41 | 42 | interface ResetListAction { 43 | type: typeof RESET_LIST; 44 | } 45 | 46 | export type SortingActions = 47 | | InitSortAction 48 | | ChangeLowestAction 49 | | ChangeHighestAction 50 | | ClearSortAction 51 | | ChangeSortingListAction 52 | | ChangeListSizeAction 53 | | ResetListAction; 54 | -------------------------------------------------------------------------------- /src/store/state.ts: -------------------------------------------------------------------------------- 1 | import { AppState } from './app/state'; 2 | import { GraphState } from './graph/state'; 3 | import { SortingState } from './sorting/state'; 4 | 5 | export interface AlgorithmVisualizerState { 6 | graph: GraphState; 7 | sorting: SortingState; 8 | app: AppState; 9 | } 10 | -------------------------------------------------------------------------------- /src/tests/unit/algorithms/graph-algorithms/graph.test.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from '../../../../algorithms/graph-algorithms/graph'; 2 | 3 | const emptyGraph: Graph = { 4 | numberOfNodes: 0, 5 | nodes: [], 6 | edges: {}, 7 | }; 8 | 9 | test('check defined empty graph has empty properties', () => { 10 | expect(emptyGraph.nodes).toStrictEqual([]); 11 | expect(emptyGraph.edges).toStrictEqual({}); 12 | expect(emptyGraph.numberOfNodes).toBe(0); 13 | }); 14 | -------------------------------------------------------------------------------- /src/tests/unit/store/app/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { changeAlgorithm, changeRunningState } from '../../../../store/app/actions'; 2 | import { AppActionTypes, CHANGE_ALGORITHM, CHANGE_RUNNING_STATE } from '../../../../store/app/types'; 3 | import { NO_ALGORITHM } from '../../../../utils/types/graph-types/graph-algorithm-types'; 4 | 5 | describe('app actions', () => { 6 | it('should create an action to change the running state', () => { 7 | const newRunningState = false; 8 | const changeRunningStateAction: AppActionTypes = { 9 | type: CHANGE_RUNNING_STATE, 10 | state: newRunningState, 11 | }; 12 | expect(changeRunningState(newRunningState)).toEqual(changeRunningStateAction); 13 | }); 14 | 15 | it('should create an action to change the selected algorithm', () => { 16 | const newAlgorithm = NO_ALGORITHM; 17 | const changeAlgorithmAction: AppActionTypes = { 18 | type: CHANGE_ALGORITHM, 19 | algorithm: newAlgorithm, 20 | }; 21 | 22 | expect(changeAlgorithm(newAlgorithm)).toEqual(changeAlgorithmAction); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/tests/unit/store/app/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { appReducer } from '../../../../store/app/reducer'; 2 | import { CHANGE_ALGORITHM, CHANGE_RUNNING_STATE, CHANGE_SPEED } from '../../../../store/app/types'; 3 | import { HIGH_SPEED, MEDIUM_SPEED } from '../../../../utils/types/app-types/alg-speed-type'; 4 | import { DIJKSTRA_ALGORITHM, NO_ALGORITHM } from '../../../../utils/types/graph-types/graph-algorithm-types'; 5 | 6 | describe('app reducer', () => { 7 | it('should handle CHANGE_RUNNING_STATE', () => { 8 | expect( 9 | appReducer(undefined, { 10 | type: CHANGE_RUNNING_STATE, 11 | state: true, 12 | }), 13 | ).toEqual({ 14 | selectedAlg: NO_ALGORITHM, 15 | speed: MEDIUM_SPEED, 16 | running: true, 17 | }); 18 | }); 19 | 20 | it('should handle CHANGE_ALGORITHM', () => { 21 | expect( 22 | appReducer(undefined, { 23 | type: CHANGE_ALGORITHM, 24 | algorithm: DIJKSTRA_ALGORITHM, 25 | }), 26 | ).toEqual({ 27 | selectedAlg: DIJKSTRA_ALGORITHM, 28 | speed: MEDIUM_SPEED, 29 | running: false, 30 | }); 31 | }); 32 | 33 | it('should handle CHANGE_SPEED', () => { 34 | expect( 35 | appReducer(undefined, { 36 | type: CHANGE_SPEED, 37 | speed: HIGH_SPEED, 38 | }), 39 | ).toEqual({ 40 | selectedAlg: NO_ALGORITHM, 41 | speed: HIGH_SPEED, 42 | running: false, 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/tests/unit/store/graph/actions.test.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode } from '../../../../algorithms/graph-algorithms/graph'; 2 | import { 3 | addNode, 4 | deleteNode, 5 | initGraph, 6 | changeDestinationNode, 7 | changeSourceNode, 8 | } from '../../../../store/graph/actions'; 9 | import { 10 | ADD_NODE, 11 | CHANGE_DESTINATION_NODE, 12 | CHANGE_SOURCE_NODE, 13 | DELETE_NODE, 14 | GraphActionTypes, 15 | INIT_GRAPH, 16 | } from '../../../../store/graph/types'; 17 | import { TableNodeType } from '../../../../utils/types/graph-types/table-node-type'; 18 | 19 | describe('graph actions', () => { 20 | it('should create an action to add a node', () => { 21 | const node = { id: '0' } as GraphNode; 22 | const table: TableNodeType[][] = [[]]; 23 | const addNodeAction: GraphActionTypes = { 24 | type: ADD_NODE, 25 | node: node, 26 | table: table, 27 | }; 28 | expect(addNode(node, table)).toEqual(addNodeAction); 29 | }); 30 | 31 | it('should create an action to delete a node', () => { 32 | const node = { id: '0' } as GraphNode; 33 | const deleteNodeAction: GraphActionTypes = { 34 | type: DELETE_NODE, 35 | node: node, 36 | }; 37 | expect(deleteNode(node)).toEqual(deleteNodeAction); 38 | }); 39 | 40 | it('should create an action to initialize the graph', () => { 41 | const width = 0; 42 | const height = 0; 43 | const initGraphAction: GraphActionTypes = { 44 | type: INIT_GRAPH, 45 | height: height, 46 | width: width, 47 | }; 48 | expect(initGraph(height, width)).toEqual(initGraphAction); 49 | }); 50 | 51 | it('should create an action to change source node', () => { 52 | const newSource = { id: '0' } as GraphNode; 53 | const changeSourceAction: GraphActionTypes = { 54 | type: CHANGE_SOURCE_NODE, 55 | newSource: newSource, 56 | }; 57 | expect(changeSourceNode(newSource)).toEqual(changeSourceAction); 58 | }); 59 | 60 | it('should create an action to change destination node', () => { 61 | const newDest = { id: '0' } as GraphNode; 62 | const changeDestAction: GraphActionTypes = { 63 | type: CHANGE_DESTINATION_NODE, 64 | newDest: newDest, 65 | }; 66 | expect(changeDestinationNode(newDest)).toEqual(changeDestAction); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/tests/unit/store/graph/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { Graph, GraphNode } from '../../../../algorithms/graph-algorithms/graph'; 2 | import { graphReducer, initialGraphState } from '../../../../store/graph/reducer'; 3 | import { GraphState } from '../../../../store/graph/state'; 4 | import { ADD_NODE, DELETE_NODE, INIT_GRAPH } from '../../../../store/graph/types'; 5 | import { SIMPLE_NODE } from '../../../../utils/types/graph-types/node-type'; 6 | import { TableNodeType } from '../../../../utils/types/graph-types/table-node-type'; 7 | 8 | const width = 3; 9 | const height = 3; 10 | 11 | const graphfor3x3: Graph = { 12 | numberOfNodes: 9, 13 | nodes: Array.from({ length: 9 }, (_, i) => i).map((elem) => ({ id: `${elem}` } as GraphNode)), 14 | 15 | edges: { 16 | '0': [{ id: '1' }, { id: '3' }], 17 | '1': [{ id: '0' }, { id: '2' }, { id: '4' }], 18 | '2': [{ id: '1' }, { id: '5' }], 19 | '3': [{ id: '0' }, { id: '4' }, { id: '6' }], 20 | '4': [{ id: '1' }, { id: '3' }, { id: '5' }, { id: '7' }], 21 | '5': [{ id: '2' }, { id: '4' }, { id: '8' }], 22 | '6': [{ id: '3' }, { id: '7' }], 23 | '7': [{ id: '4' }, { id: '6' }, { id: '8' }], 24 | '8': [{ id: '5' }, { id: '7' }], 25 | }, 26 | }; 27 | 28 | const initData = (height: number, width: number): TableNodeType[][] => { 29 | return Array(height) 30 | .fill(null) 31 | .map(() => Array(width).fill({ nodeType: SIMPLE_NODE })); 32 | }; 33 | 34 | const table3x3: TableNodeType[][] = initData(height, width); 35 | 36 | describe('graph reducer', () => { 37 | it('should handle INIT_GRAPH', () => { 38 | const newState: GraphState = graphReducer(undefined, { 39 | type: INIT_GRAPH, 40 | width: width, 41 | height: height, 42 | }); 43 | 44 | expect(newState.numberOfNodes).toEqual(graphfor3x3.numberOfNodes); 45 | expect(newState.nodes).toEqual(graphfor3x3.nodes); 46 | expect(newState.edges).toEqual(graphfor3x3.edges); 47 | }); 48 | 49 | it('should handle DELETE_NODE in midele', () => { 50 | const newState: GraphState = graphReducer( 51 | { 52 | ...initialGraphState, 53 | width: 3, 54 | height: 3, 55 | nodes: graphfor3x3.nodes, 56 | edges: graphfor3x3.edges, 57 | numberOfNodes: graphfor3x3.numberOfNodes, 58 | }, 59 | { 60 | type: DELETE_NODE, 61 | node: { id: '4' }, 62 | }, 63 | ); 64 | expect(newState.nodes).not.toContainEqual({ id: '4' }); 65 | expect(newState.nodes.length).toEqual(graphfor3x3.nodes.length - 1); 66 | expect(newState.edges['4']).toBe(undefined); 67 | graphfor3x3.edges['4'].forEach((elem: GraphNode) => { 68 | expect(newState.edges[elem.id]).not.toContainEqual({ id: '4' }); 69 | }); 70 | }); 71 | 72 | it('should handle DELETE_NODE at side', () => { 73 | const newState: GraphState = graphReducer( 74 | { 75 | ...initialGraphState, 76 | width: 3, 77 | height: 3, 78 | nodes: graphfor3x3.nodes, 79 | edges: graphfor3x3.edges, 80 | numberOfNodes: graphfor3x3.numberOfNodes, 81 | }, 82 | { 83 | type: DELETE_NODE, 84 | node: { id: '8' }, 85 | }, 86 | ); 87 | expect(newState.nodes).not.toContainEqual({ id: '8' }); 88 | expect(newState.nodes.length).toEqual(graphfor3x3.nodes.length - 1); 89 | expect(newState.edges['8']).toBe(undefined); 90 | graphfor3x3.edges['8'].forEach((elem: GraphNode) => { 91 | expect(newState.edges[elem.id]).not.toContainEqual({ id: '8' }); 92 | }); 93 | }); 94 | 95 | it('should handle DELETE_NODE only once', () => { 96 | const newState: GraphState = graphReducer( 97 | { 98 | ...initialGraphState, 99 | width: 3, 100 | height: 3, 101 | nodes: graphfor3x3.nodes, 102 | edges: graphfor3x3.edges, 103 | numberOfNodes: graphfor3x3.numberOfNodes, 104 | }, 105 | { 106 | type: DELETE_NODE, 107 | node: { id: '8' }, 108 | }, 109 | ); 110 | 111 | expect(newState.nodes).not.toContainEqual({ id: '8' }); 112 | expect(newState.nodes.length).toEqual(graphfor3x3.nodes.length - 1); 113 | expect(newState.edges['8']).toBe(undefined); 114 | graphfor3x3.edges['8'].forEach((elem: GraphNode) => { 115 | expect(newState.edges[elem.id]).not.toContainEqual({ id: '8' }); 116 | }); 117 | 118 | const tryDeleteNodeAgainState = graphReducer(newState, { type: DELETE_NODE, node: { id: '8' } }); 119 | 120 | expect(tryDeleteNodeAgainState.nodes).not.toContainEqual({ id: '8' }); 121 | expect(tryDeleteNodeAgainState.nodes.length).toEqual(graphfor3x3.nodes.length - 1); 122 | expect(tryDeleteNodeAgainState.edges['8']).toBe(undefined); 123 | graphfor3x3.edges['8'].forEach((elem: GraphNode) => { 124 | expect(tryDeleteNodeAgainState.edges[elem.id]).not.toContainEqual({ id: '8' }); 125 | }); 126 | expect( 127 | Object.keys(tryDeleteNodeAgainState.edges).reduce( 128 | (sum: number, currElem: string) => sum + tryDeleteNodeAgainState.edges[currElem].length, 129 | 0, 130 | ), 131 | ).toEqual( 132 | Object.keys(graphfor3x3.edges).reduce( 133 | (sum: number, currElem: string) => sum + graphfor3x3.edges[currElem].length, 134 | 0, 135 | ) - 4, 136 | ); 137 | }); 138 | 139 | it('should handle ADD_NODE', () => { 140 | const stateRemoving4 = graphReducer( 141 | { 142 | ...initialGraphState, 143 | width: 3, 144 | height: 3, 145 | nodes: graphfor3x3.nodes, 146 | edges: graphfor3x3.edges, 147 | numberOfNodes: graphfor3x3.numberOfNodes, 148 | }, 149 | { type: DELETE_NODE, node: { id: '4' } }, 150 | ); 151 | const adding4AgainState = graphReducer(stateRemoving4, { 152 | type: ADD_NODE, 153 | node: { id: '4' }, 154 | table: table3x3, 155 | }); 156 | expect(adding4AgainState.numberOfNodes).toEqual(graphfor3x3.numberOfNodes); 157 | expect( 158 | Object.keys(adding4AgainState.edges).reduce( 159 | (sum: number, currElem: string) => sum + adding4AgainState.edges[currElem].length, 160 | 0, 161 | ), 162 | ).toEqual( 163 | Object.keys(graphfor3x3.edges).reduce( 164 | (sum: number, currElem: string) => sum + graphfor3x3.edges[currElem].length, 165 | 0, 166 | ), 167 | ); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /src/utils/app-utils-functions.ts: -------------------------------------------------------------------------------- 1 | import { toast } from 'react-toastify'; 2 | import 'react-toastify/dist/ReactToastify.css'; 3 | import { HIGH_SPEED, LOW_SPEED, MEDIUM_SPEED, SpeedType } from './types/app-types/alg-speed-type'; 4 | 5 | export const generateRandomNumber = (start: number, end: number): number => { 6 | start = Math.ceil(start); 7 | end = Math.floor(end); 8 | 9 | return Math.floor(Math.random() * (end - start) + start); 10 | }; 11 | 12 | export const createErrorToast = (err: string): void => { 13 | toast.error(err, { 14 | position: 'bottom-right', 15 | autoClose: 5000, 16 | hideProgressBar: false, 17 | closeOnClick: true, 18 | pauseOnHover: true, 19 | draggable: true, 20 | progress: undefined, 21 | }); 22 | }; 23 | 24 | export const speedStrToSpeed = (newSpeed: string): SpeedType => { 25 | switch (newSpeed) { 26 | case 'Low Speed': 27 | return LOW_SPEED; 28 | case 'Medium Speed': 29 | return MEDIUM_SPEED; 30 | case 'High Speed': 31 | return HIGH_SPEED; 32 | default: 33 | return MEDIUM_SPEED; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/graph-utils-functions.ts: -------------------------------------------------------------------------------- 1 | import { bfs } from '../algorithms/graph-algorithms/bfs'; 2 | import { Graph, GraphNode, ParentVectorType } from '../algorithms/graph-algorithms/graph'; 3 | import { 4 | A_STAR, 5 | BEST_FIRST_SEARCH, 6 | BREADTH_FIRST_SEARCH, 7 | DIJKSTRA_ALGORITHM, 8 | GraphAlgoirhtmsType, 9 | NO_ALGORITHM, 10 | } from './types/graph-types/graph-algorithm-types'; 11 | import { 12 | DESTINATION_NODE, 13 | SHORTEST_PATH_NODE, 14 | SIMPLE_NODE, 15 | SOURCE_NODE, 16 | VISITED_NODE, 17 | VISITED_WEIGHT_NODE, 18 | VISITED_WEIGHT_SHORTEST_PATH_NODE, 19 | WALL_NODE, 20 | WEIGHTED_NODE, 21 | } from './types/graph-types/node-type'; 22 | import { 23 | DESTINATION_NODE_BUTTON, 24 | NodeTypeButtonType, 25 | RESTORE_NODE_BUTTON, 26 | SOURCE_NODE_BUTTON, 27 | WALL_NODE_BUTTON, 28 | WEIGHTED_NODE_BUTTON, 29 | } from './types/graph-types/node-type-button-type'; 30 | import { GraphState } from '../store/graph/state'; 31 | import { GraphAlgorithmResult, GraphAlgOutput, Pair } from './types/graph-types/graph-results-types'; 32 | import { dijkstra } from '../algorithms/graph-algorithms/dijkstra'; 33 | 34 | import { aStar } from '../algorithms/graph-algorithms/a_star'; 35 | import { bestFirstSearch } from '../algorithms/graph-algorithms/best-first-search'; 36 | import { AlgorithmType } from './types/app-types/algorithm-classes-types'; 37 | import { createErrorToast } from './app-utils-functions'; 38 | import { graphAlgorithms } from './types/graph-types/consts'; 39 | import { TableNodeType } from './types/graph-types/table-node-type'; 40 | 41 | export const validCoords = (x: number, y: number, height: number, width: number): boolean => { 42 | return x >= 0 && y >= 0 && x < height && y < width; 43 | }; 44 | 45 | export const fromIndexToPair = (index: number, width: number, weight?: number): Pair => { 46 | return { 47 | row: (index / width) | 0, 48 | col: index % width, 49 | weight: weight, 50 | }; 51 | }; 52 | 53 | export const fromPairToIndex = (pair: Pair, width: number): number => { 54 | return pair.row * width + pair.col; 55 | }; 56 | 57 | export const copyTableImmutable = (table: TableNodeType[][]): TableNodeType[][] => { 58 | return table.map((arr: TableNodeType[]) => { 59 | return arr.map((elem: TableNodeType) => ({ ...elem } as TableNodeType)); 60 | }) as TableNodeType[][]; 61 | }; 62 | 63 | export const wasAlgorithmRunning = (table: TableNodeType[][]): boolean => { 64 | return table.some((row: TableNodeType[]) => 65 | row.some( 66 | (elem: TableNodeType) => 67 | elem.nodeType === VISITED_NODE || 68 | elem.nodeType === VISITED_WEIGHT_NODE || 69 | elem.nodeType === SHORTEST_PATH_NODE || 70 | elem.nodeType === VISITED_WEIGHT_SHORTEST_PATH_NODE, 71 | ), 72 | ); 73 | }; 74 | 75 | export const getNewGrid = ( 76 | table: TableNodeType[][], 77 | activeNodeTypeButton: NodeTypeButtonType, 78 | x: number, 79 | y: number, 80 | ): TableNodeType[][] => { 81 | switch (activeNodeTypeButton) { 82 | case WALL_NODE_BUTTON: { 83 | if (table[x][y].nodeType !== SIMPLE_NODE && table[x][y].nodeType !== WEIGHTED_NODE) { 84 | return table as TableNodeType[][]; 85 | } 86 | const newTable = copyTableImmutable(table); 87 | newTable[x][y] = { nodeType: WALL_NODE } as TableNodeType; 88 | return newTable; 89 | } 90 | case WEIGHTED_NODE_BUTTON: { 91 | if (table[x][y].nodeType !== SIMPLE_NODE && table[x][y].nodeType !== WALL_NODE) { 92 | return table as TableNodeType[][]; 93 | } 94 | 95 | const newTable = copyTableImmutable(table); 96 | newTable[x][y] = { nodeType: WEIGHTED_NODE, weight: 10 } as TableNodeType; 97 | return newTable; 98 | } 99 | case RESTORE_NODE_BUTTON: { 100 | if ( 101 | table[x][y].nodeType === SOURCE_NODE || 102 | table[x][y].nodeType === DESTINATION_NODE || 103 | table[x][y].nodeType === SIMPLE_NODE || 104 | table[x][y].nodeType === VISITED_NODE || 105 | table[x][y].nodeType === VISITED_WEIGHT_NODE || 106 | table[x][y].nodeType === SHORTEST_PATH_NODE || 107 | table[x][y].nodeType === VISITED_WEIGHT_SHORTEST_PATH_NODE 108 | ) { 109 | return table as TableNodeType[][]; 110 | } 111 | 112 | const newTable = copyTableImmutable(table); 113 | newTable[x][y].nodeType = SIMPLE_NODE; 114 | newTable[x][y].weight = undefined; 115 | return newTable; 116 | } 117 | case SOURCE_NODE_BUTTON: { 118 | if (table[x][y].nodeType !== SIMPLE_NODE) { 119 | return table as TableNodeType[][]; 120 | } 121 | if (wasAlgorithmRunning(table)) { 122 | createErrorToast('You cannot move the source node, on a visited graph'); 123 | return table as TableNodeType[][]; 124 | } 125 | const newTable = copyTableImmutable(table).map((row: TableNodeType[]): TableNodeType[] => { 126 | return row.map( 127 | (elem: TableNodeType): TableNodeType => { 128 | if (elem.nodeType === SOURCE_NODE) { 129 | return { nodeType: SIMPLE_NODE } as TableNodeType; 130 | } 131 | return elem as TableNodeType; 132 | }, 133 | ); 134 | }); 135 | 136 | newTable[x][y].nodeType = SOURCE_NODE; 137 | return newTable; 138 | } 139 | case DESTINATION_NODE_BUTTON: { 140 | const condition = !window.experimental 141 | ? table[x][y].nodeType !== SIMPLE_NODE 142 | : table[x][y].nodeType !== SIMPLE_NODE && 143 | table[x][y].nodeType !== VISITED_NODE && 144 | table[x][y].nodeType !== SHORTEST_PATH_NODE; 145 | 146 | if (condition) { 147 | return table as TableNodeType[][]; 148 | } 149 | 150 | if (!window.experimental && wasAlgorithmRunning(table)) { 151 | createErrorToast('You cannot move the destination node, on a visited graph'); 152 | return table as TableNodeType[][]; 153 | } 154 | 155 | const newTable = copyTableImmutable(table).map((row: TableNodeType[]): TableNodeType[] => { 156 | return row.map( 157 | (elem: TableNodeType): TableNodeType => { 158 | if (elem.nodeType === DESTINATION_NODE) { 159 | return { nodeType: SIMPLE_NODE } as TableNodeType; 160 | } 161 | return elem as TableNodeType; 162 | }, 163 | ); 164 | }); 165 | 166 | newTable[x][y] = { nodeType: DESTINATION_NODE }; 167 | return newTable; 168 | } 169 | default: 170 | return table; 171 | } 172 | }; 173 | 174 | export const reduxGraphUpdateDispatchHelper = ( 175 | table: TableNodeType[][], 176 | activeNodeButton: NodeTypeButtonType, 177 | changeSourceNodeHandler: (x: GraphNode) => void, 178 | changeDestinationNodeHandler: (x: GraphNode) => void, 179 | deleteNodeHandler: (node: GraphNode) => void, 180 | addNodeHandler: (node: GraphNode, table: TableNodeType[][]) => void, 181 | addWeightedNodeHandler: (node: GraphNode, table: TableNodeType[][]) => void, 182 | x: number, 183 | y: number, 184 | width: number, 185 | weight?: number, 186 | ): void => { 187 | switch (activeNodeButton) { 188 | case SOURCE_NODE_BUTTON: 189 | if (table[x][y].nodeType !== SIMPLE_NODE) { 190 | break; 191 | } 192 | if (wasAlgorithmRunning(table)) { 193 | break; 194 | } 195 | changeSourceNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}` }); 196 | 197 | break; 198 | case DESTINATION_NODE_BUTTON: { 199 | const condition = !window.experimental 200 | ? table[x][y].nodeType !== SIMPLE_NODE 201 | : table[x][y].nodeType !== SIMPLE_NODE && 202 | table[x][y].nodeType !== VISITED_NODE && 203 | table[x][y].nodeType !== SHORTEST_PATH_NODE; 204 | 205 | if (condition) { 206 | break; 207 | } 208 | 209 | if (!window.experimental && wasAlgorithmRunning(table)) { 210 | break; 211 | } 212 | 213 | changeDestinationNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}` }); 214 | break; 215 | } 216 | case WALL_NODE_BUTTON: 217 | if (table[x][y].nodeType !== SIMPLE_NODE && table[x][y].nodeType !== WEIGHTED_NODE) { 218 | break; 219 | } 220 | 221 | deleteNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}` }); 222 | break; 223 | case WEIGHTED_NODE_BUTTON: { 224 | if (table[x][y].nodeType !== SIMPLE_NODE && table[x][y].nodeType !== WALL_NODE) { 225 | break; 226 | } 227 | addWeightedNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}`, weight: weight }, table); 228 | break; 229 | } 230 | case RESTORE_NODE_BUTTON: 231 | if ( 232 | table[x][y].nodeType === SOURCE_NODE || 233 | table[x][y].nodeType === DESTINATION_NODE || 234 | table[x][y].nodeType === SIMPLE_NODE || 235 | table[x][y].nodeType === VISITED_NODE || 236 | table[x][y].nodeType === VISITED_WEIGHT_NODE || 237 | table[x][y].nodeType === SHORTEST_PATH_NODE || 238 | table[x][y].nodeType === VISITED_WEIGHT_SHORTEST_PATH_NODE 239 | ) { 240 | break; 241 | } 242 | deleteNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}` }); 243 | addNodeHandler({ id: `${fromPairToIndex({ row: x, col: y }, width)}` }, table); 244 | break; 245 | default: 246 | break; 247 | } 248 | }; 249 | 250 | export const isBlockedNode = (coords: Pair, table: TableNodeType[][]): boolean => { 251 | return table[coords.row][coords.col].nodeType === WALL_NODE; 252 | }; 253 | 254 | export const algNameToAlgType = (algName: string): GraphAlgoirhtmsType => { 255 | switch (algName) { 256 | case "Dijkstra's Algorithm": { 257 | return DIJKSTRA_ALGORITHM as GraphAlgoirhtmsType; 258 | } 259 | case 'Breadth First Search': { 260 | return BREADTH_FIRST_SEARCH as GraphAlgoirhtmsType; 261 | } 262 | case 'A* Algorithm': { 263 | return A_STAR; 264 | } 265 | case 'Best First Search': { 266 | return BEST_FIRST_SEARCH; 267 | } 268 | default: 269 | return NO_ALGORITHM as GraphAlgoirhtmsType; 270 | } 271 | }; 272 | 273 | const fromGraphNodesToPairs = (graphNodes: GraphNode[], width: number): Pair[] => { 274 | return graphNodes.map((elem: GraphNode) => fromIndexToPair(parseInt(elem.id, 10), width, elem.weight)); 275 | }; 276 | 277 | export const getShortestPath = ( 278 | source: GraphNode, 279 | destination: GraphNode, 280 | parentVector: ParentVectorType, 281 | ): GraphNode[] => { 282 | const result: GraphNode[] = []; 283 | let currentNode = destination; 284 | while (currentNode !== undefined && currentNode.id !== source.id) { 285 | result.push({ ...currentNode }); 286 | currentNode = parentVector[currentNode.id]; 287 | } 288 | result.shift(); 289 | return result.reverse(); 290 | }; 291 | 292 | export const getVisitedNodes = (algType: AlgorithmType, graphState: GraphState): GraphAlgorithmResult => { 293 | switch (algType) { 294 | case BREADTH_FIRST_SEARCH: 295 | if (graphState.source && graphState.destination) { 296 | const { visitedNodes, parentVector }: GraphAlgOutput = bfs(graphState.source, graphState.destination, { 297 | numberOfNodes: graphState.numberOfNodes, 298 | nodes: graphState.nodes, 299 | edges: graphState.edges, 300 | } as Graph); 301 | return { 302 | visitedNodesInOrder: fromGraphNodesToPairs(visitedNodes, graphState.width), 303 | shortestPath: fromGraphNodesToPairs( 304 | getShortestPath(graphState.source, graphState.destination, parentVector), 305 | graphState.width, 306 | ), 307 | }; 308 | } 309 | return { visitedNodesInOrder: [], shortestPath: [] }; 310 | case DIJKSTRA_ALGORITHM: 311 | if (graphState.source && graphState.destination) { 312 | const { visitedNodes, parentVector }: GraphAlgOutput = dijkstra( 313 | graphState.source, 314 | graphState.destination, 315 | { 316 | numberOfNodes: graphState.numberOfNodes, 317 | nodes: graphState.nodes, 318 | edges: graphState.edges, 319 | } as Graph, 320 | ); 321 | return { 322 | visitedNodesInOrder: fromGraphNodesToPairs(visitedNodes, graphState.width), 323 | shortestPath: fromGraphNodesToPairs( 324 | getShortestPath(graphState.source, graphState.destination, parentVector), 325 | graphState.width, 326 | ), 327 | }; 328 | } 329 | return { visitedNodesInOrder: [], shortestPath: [] }; 330 | case A_STAR: 331 | if (graphState.source && graphState.destination) { 332 | const { visitedNodes, parentVector }: GraphAlgOutput = aStar( 333 | graphState.source, 334 | graphState.destination, 335 | graphState, 336 | ); 337 | return { 338 | visitedNodesInOrder: fromGraphNodesToPairs(visitedNodes, graphState.width), 339 | shortestPath: fromGraphNodesToPairs( 340 | getShortestPath(graphState.source, graphState.destination, parentVector), 341 | graphState.width, 342 | ), 343 | }; 344 | } 345 | return { visitedNodesInOrder: [], shortestPath: [] }; 346 | case BEST_FIRST_SEARCH: 347 | if (graphState.source && graphState.destination) { 348 | const { visitedNodes, parentVector }: GraphAlgOutput = bestFirstSearch( 349 | graphState.source, 350 | graphState.destination, 351 | graphState, 352 | ); 353 | return { 354 | visitedNodesInOrder: fromGraphNodesToPairs(visitedNodes, graphState.width), 355 | shortestPath: fromGraphNodesToPairs( 356 | getShortestPath(graphState.source, graphState.destination, parentVector), 357 | graphState.width, 358 | ), 359 | }; 360 | } 361 | return { visitedNodesInOrder: [], shortestPath: [] }; 362 | default: 363 | return { visitedNodesInOrder: [], shortestPath: [] }; 364 | } 365 | }; 366 | 367 | export const checkCanPutWeight = ( 368 | currentSelectedAlg: AlgorithmType, 369 | currentSelectedButton: NodeTypeButtonType, 370 | ): boolean => { 371 | return !( 372 | (currentSelectedAlg === BREADTH_FIRST_SEARCH || currentSelectedAlg === BEST_FIRST_SEARCH) && 373 | currentSelectedButton === WEIGHTED_NODE_BUTTON 374 | ); 375 | }; 376 | 377 | export const computeDistance = (p1: Pair, p2: Pair): number => { 378 | return Math.sqrt((p1.row - p2.row) * (p1.row - p2.row) + (p1.col - p2.col) * (p1.col - p2.col)); 379 | }; 380 | 381 | export const algorithmDoesNoatAcceptWeights = (table: TableNodeType[][], selectedAlg: AlgorithmType): boolean => { 382 | const existsWeights = table.some((row: TableNodeType[]) => 383 | row.some((elem: TableNodeType) => elem.nodeType === WEIGHTED_NODE), 384 | ); 385 | if (!existsWeights) { 386 | return false; 387 | } 388 | const notSupportingWeights: GraphAlgoirhtmsType[] = [BEST_FIRST_SEARCH, BREADTH_FIRST_SEARCH]; 389 | 390 | return !(notSupportingWeights.filter((alg: GraphAlgoirhtmsType) => alg === selectedAlg).length === 0); 391 | }; 392 | 393 | export const getWeightFromNode = (adjId: string, nodes: GraphNode[]): number | undefined => { 394 | let weight = undefined; 395 | nodes.forEach((node: GraphNode) => { 396 | if (node.id === adjId) { 397 | weight = node.weight; 398 | } 399 | }); 400 | return weight; 401 | }; 402 | 403 | export const isGraphAlgorithm = (selectedAlg: AlgorithmType): boolean => { 404 | return graphAlgorithms.some((elem: GraphAlgoirhtmsType) => elem === selectedAlg); 405 | }; 406 | -------------------------------------------------------------------------------- /src/utils/sorting-utils-functions.ts: -------------------------------------------------------------------------------- 1 | import { bubbleSort } from '../algorithms/sorting-algorithms/bubble-sort'; 2 | import { heapSort } from '../algorithms/sorting-algorithms/heap-sort'; 3 | import { mergeSort } from '../algorithms/sorting-algorithms/merge-sort'; 4 | import { quickSort } from '../algorithms/sorting-algorithms/quick-sort'; 5 | import { SortingState } from '../store/sorting/state'; 6 | import { AlgorithmType } from './types/app-types/algorithm-classes-types'; 7 | import { ArrayStackType } from './types/sorting-types/array-stack-type'; 8 | import { sortingALgorithms } from './types/sorting-types/consts'; 9 | import { 10 | BUBBLE_SORT, 11 | HEAP_SORT, 12 | MERGE_SORT, 13 | QUICK_SORT, 14 | SortingAlgorithmsType, 15 | } from './types/sorting-types/sorting-alorithm-types'; 16 | import { SortingAlgorithmResult } from './types/sorting-types/sorting-results-types'; 17 | 18 | export const sortNameToSortType = (name: string): SortingAlgorithmsType => { 19 | switch (name) { 20 | case 'Bubble Sort': 21 | return BUBBLE_SORT; 22 | case 'Quick Sort': 23 | return QUICK_SORT; 24 | case 'Merge Sort': 25 | return MERGE_SORT; 26 | case 'Heap Sort': 27 | return HEAP_SORT; 28 | default: 29 | return BUBBLE_SORT; //TODO: come up with an abstraction for NO_ALGORITHM 30 | } 31 | }; 32 | 33 | export const isSortingAlgorithm = (alg: AlgorithmType): boolean => { 34 | return sortingALgorithms.some((elem: AlgorithmType) => elem === alg); 35 | }; 36 | 37 | export const getSorginAlgorithmOutput = (alg: AlgorithmType, sortingState: SortingState): SortingAlgorithmResult => { 38 | switch (alg) { 39 | case BUBBLE_SORT: { 40 | return { ...bubbleSort(sortingState) }; 41 | } 42 | case MERGE_SORT: { 43 | return { ...mergeSort(sortingState) }; 44 | } 45 | case QUICK_SORT: { 46 | return { ...quickSort(sortingState) }; 47 | } 48 | case HEAP_SORT: { 49 | return { ...heapSort(sortingState) }; 50 | } 51 | default: 52 | return { output: [] }; 53 | } 54 | }; 55 | 56 | export const copyNumbersImmutable = (numbers: ArrayStackType[]): ArrayStackType[] => { 57 | return [...numbers.map((elem: ArrayStackType) => ({ ...elem }))]; 58 | }; 59 | -------------------------------------------------------------------------------- /src/utils/types/app-types/alg-speed-type.ts: -------------------------------------------------------------------------------- 1 | export const LOW_SPEED = 'Low Speed'; 2 | export const MEDIUM_SPEED = 'Medium Speed'; 3 | export const HIGH_SPEED = 'High Speed'; 4 | 5 | export type SpeedType = typeof LOW_SPEED | typeof MEDIUM_SPEED | typeof HIGH_SPEED; 6 | -------------------------------------------------------------------------------- /src/utils/types/app-types/algorithm-classes-types.ts: -------------------------------------------------------------------------------- 1 | import { GraphAlgoirhtmsType } from '../graph-types/graph-algorithm-types'; 2 | import { SortingAlgorithmsType } from '../sorting-types/sorting-alorithm-types'; 3 | 4 | export type AlgDropdownOption = { 5 | key: string; 6 | value: string; 7 | text: string; 8 | }; 9 | 10 | export type AlgorithmType = GraphAlgoirhtmsType | SortingAlgorithmsType; 11 | -------------------------------------------------------------------------------- /src/utils/types/app-types/consts.ts: -------------------------------------------------------------------------------- 1 | export type speedDropdownOption = { 2 | key: string; 3 | value: string; 4 | text: string; 5 | }; 6 | 7 | export const speedDropdownOptions: speedDropdownOption[] = [ 8 | { 9 | key: 'Low Speed', 10 | value: 'Low Speed', 11 | text: 'Low Speed', 12 | }, 13 | { 14 | key: 'Medium Speed', 15 | value: 'Medium Speed', 16 | text: 'Medium Speed', 17 | }, 18 | { 19 | key: 'High Speed', 20 | value: 'High Speed', 21 | text: 'High Speed', 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /src/utils/types/app-types/dictionary.ts: -------------------------------------------------------------------------------- 1 | export interface MyDictionary { 2 | [Key: string]: T; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/types/app-types/window.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | interface Window { 5 | experimental: boolean; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/consts.ts: -------------------------------------------------------------------------------- 1 | import wall from '../../../assets/images/wall.png'; 2 | import weight from '../../../assets/images/weight.png'; 3 | import simple from '../../../assets/images/simple.png'; 4 | import { RESTORE_NODE_BUTTON, WALL_NODE_BUTTON, WEIGHTED_NODE_BUTTON } from './node-type-button-type'; 5 | import { 6 | A_STAR, 7 | BEST_FIRST_SEARCH, 8 | BREADTH_FIRST_SEARCH, 9 | DIJKSTRA_ALGORITHM, 10 | GraphAlgoirhtmsType, 11 | } from './graph-algorithm-types'; 12 | import { AlgDropdownOption } from '../app-types/algorithm-classes-types'; 13 | 14 | export const addNodesDropdownOptions = [ 15 | { 16 | key: 'Add wall nodes', 17 | text: 'Add wall nodes', 18 | value: WALL_NODE_BUTTON, 19 | image: { avatar: true, src: wall }, 20 | }, 21 | { 22 | key: 'Add weighted nodes', 23 | text: 'Add weighted nodes', 24 | value: WEIGHTED_NODE_BUTTON, 25 | image: { avatar: true, src: weight }, 26 | }, 27 | { 28 | key: 'Restore nodes', 29 | text: 'Restore nodes', 30 | value: RESTORE_NODE_BUTTON, 31 | image: { avatar: true, src: simple }, 32 | }, 33 | ]; 34 | 35 | export const graphAlgDropdownOptions: AlgDropdownOption[] = [ 36 | { 37 | key: 'Breadth First Search', 38 | value: 'Breadth First Search', 39 | text: 'Breadth First Search', 40 | }, 41 | { 42 | key: "Dijkstra's Algorithm", 43 | value: "Dijkstra's Algorithm", 44 | text: "Dijkstra's Algorithm", 45 | }, 46 | 47 | { 48 | key: 'A* Alrogithm', 49 | value: 'A* Algorithm', 50 | text: 'A* Algorithm', 51 | }, 52 | { 53 | key: 'Best First Search', 54 | value: 'Best First Search', 55 | text: 'Best First Search', 56 | }, 57 | ]; 58 | 59 | export const graphAlgorithms: GraphAlgoirhtmsType[] = [ 60 | BREADTH_FIRST_SEARCH, 61 | DIJKSTRA_ALGORITHM, 62 | A_STAR, 63 | BEST_FIRST_SEARCH, 64 | ]; 65 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/graph-algorithm-types.ts: -------------------------------------------------------------------------------- 1 | export const NO_ALGORITHM = 'Choose an Algorithm'; 2 | export const DIJKSTRA_ALGORITHM = "Dijkstra's Algorithm"; 3 | export const BREADTH_FIRST_SEARCH = 'Breadth First Search'; 4 | export const A_STAR = 'A* Algorithm'; 5 | export const BEST_FIRST_SEARCH = 'Best First Search'; 6 | 7 | export type GraphAlgoirhtmsType = 8 | | typeof DIJKSTRA_ALGORITHM 9 | | typeof BREADTH_FIRST_SEARCH 10 | | typeof NO_ALGORITHM 11 | | typeof A_STAR 12 | | typeof BEST_FIRST_SEARCH; 13 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/graph-results-types.ts: -------------------------------------------------------------------------------- 1 | import { GraphNode, ParentVectorType } from '../../../algorithms/graph-algorithms/graph'; 2 | 3 | export type GraphAlgOutput = { 4 | visitedNodes: GraphNode[]; 5 | parentVector: ParentVectorType; 6 | }; 7 | 8 | export type GraphAlgorithmResult = { 9 | visitedNodesInOrder: Pair[]; 10 | shortestPath: Pair[]; 11 | }; 12 | 13 | export type Pair = { 14 | row: number; 15 | col: number; 16 | weight?: number; 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/navigation-item-type.ts: -------------------------------------------------------------------------------- 1 | export const LINK_TYPE = 'LINK_TYPE'; 2 | export const ITEM_TYPE = 'ITEM_TYPE'; 3 | export const DIVIDER_TYPE = 'DIVIDER_TYPE'; 4 | export const BUTTON_TYPE = 'BUTTON_TYPE'; 5 | 6 | export type NavigationItemType = typeof LINK_TYPE | typeof ITEM_TYPE | typeof DIVIDER_TYPE | typeof BUTTON_TYPE; 7 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/node-type-button-type.ts: -------------------------------------------------------------------------------- 1 | export const WALL_NODE_BUTTON = 'WALL_NODE_BUTTON'; 2 | export const RESTORE_NODE_BUTTON = 'RESTORE_NODE_BUTTON'; 3 | export const WEIGHTED_NODE_BUTTON = 'WEIGHTED_NODE_BUTTON'; 4 | export const SOURCE_NODE_BUTTON = 'SOURCE_NODE_BUTTON'; 5 | export const DESTINATION_NODE_BUTTON = 'DESTINATION_NODE_BUTTON'; 6 | 7 | export type NodeTypeButtonType = 8 | | typeof WALL_NODE_BUTTON 9 | | typeof RESTORE_NODE_BUTTON 10 | | typeof WEIGHTED_NODE_BUTTON 11 | | typeof SOURCE_NODE_BUTTON 12 | | typeof DESTINATION_NODE_BUTTON; 13 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/node-type.ts: -------------------------------------------------------------------------------- 1 | export const WEIGHTED_NODE = 'WEIGHTED_NODE'; 2 | export const SIMPLE_NODE = 'SIMPLE_NODE'; 3 | export const WALL_NODE = 'WALL_NODE'; 4 | export const SOURCE_NODE = 'START_NODE'; 5 | export const DESTINATION_NODE = 'DESTINATION_NODE'; 6 | export const VISITED_NODE = 'VISITED_NODE'; 7 | export const SHORTEST_PATH_NODE = 'SHORTEST_PATH_NODE'; 8 | export const VISITED_WEIGHT_NODE = 'VISITED_WEIGHT_NODE'; 9 | export const VISITED_WEIGHT_SHORTEST_PATH_NODE = 'VISITED_WEIGHT_SHORTEST_PATH_NODE'; 10 | 11 | export type NodeType = 12 | | typeof WEIGHTED_NODE 13 | | typeof SIMPLE_NODE 14 | | typeof WALL_NODE 15 | | typeof SOURCE_NODE 16 | | typeof DESTINATION_NODE 17 | | typeof VISITED_NODE 18 | | typeof SHORTEST_PATH_NODE 19 | | typeof VISITED_WEIGHT_NODE 20 | | typeof VISITED_WEIGHT_SHORTEST_PATH_NODE; 21 | -------------------------------------------------------------------------------- /src/utils/types/graph-types/table-node-type.ts: -------------------------------------------------------------------------------- 1 | import { NodeType } from './node-type'; 2 | 3 | export type TableNodeType = { 4 | nodeType: NodeType; 5 | weight?: number; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/array-stack-type.ts: -------------------------------------------------------------------------------- 1 | import { SortingStackType } from './sorting-stack-type'; 2 | 3 | export type ArrayStackType = { 4 | elemType: SortingStackType; 5 | number: number; 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/consts.ts: -------------------------------------------------------------------------------- 1 | import { AlgDropdownOption } from '../app-types/algorithm-classes-types'; 2 | import { BUBBLE_SORT, HEAP_SORT, MERGE_SORT, QUICK_SORT, SortingAlgorithmsType } from './sorting-alorithm-types'; 3 | 4 | export const sortingAlgDropdownOptions: AlgDropdownOption[] = [ 5 | { 6 | key: 'Bubble Sort', 7 | value: 'Bubble Sort', 8 | text: 'Bubble Sort', 9 | }, 10 | { 11 | key: 'Quick Sort', 12 | value: 'Quick Sort', 13 | text: 'Quick Sort', 14 | }, 15 | 16 | { 17 | key: 'Merge Sort', 18 | value: 'Merge Sort', 19 | text: 'Merge Sort', 20 | }, 21 | { 22 | key: 'Heap Sort', 23 | value: 'Heap Sort', 24 | text: 'Heap Sort', 25 | }, 26 | ]; 27 | 28 | export const sortingALgorithms: SortingAlgorithmsType[] = [BUBBLE_SORT, QUICK_SORT, MERGE_SORT, HEAP_SORT]; 29 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/sorting-alorithm-types.ts: -------------------------------------------------------------------------------- 1 | export const BUBBLE_SORT = 'Bubble Sort'; 2 | export const QUICK_SORT = 'Quick Sort'; 3 | export const MERGE_SORT = 'Merge Sort'; 4 | export const HEAP_SORT = 'Heap Sort'; 5 | 6 | export type SortingAlgorithmsType = typeof BUBBLE_SORT | typeof QUICK_SORT | typeof MERGE_SORT | typeof HEAP_SORT; 7 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/sorting-default-values.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_STACK_HEIGHT = 100; 2 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/sorting-results-types.ts: -------------------------------------------------------------------------------- 1 | export const SWAP_PAIR = 'SWAP_PAIR'; 2 | export const PLACED_NUMBER = 'PLACED_NUMBER'; 3 | export const CURRENT_INDEX = 'CURRENT_INDEX'; 4 | export const SWAPPED_PAIR = 'SWAPPED_PAIR'; 5 | export const FINISHED_VISITING_INDEX = 'FINISH_VISITING_INDEX'; 6 | export const MERGE_PAIR = 'MERGE_PAIR'; 7 | export const FINISHED_MERGE_PAIR = 'FINISHED_MERGE_PAIR'; 8 | export const LEFT_TO_MERGE = 'LEFT_TO_MERGE'; 9 | export const FINISHED_LEFT_TO_MERGE = 'FINISHED_LEFT_TO_MERGE'; 10 | 11 | export type SwapPair = { 12 | type: typeof SWAP_PAIR; 13 | firstIndex: number; 14 | secondIndex: number; 15 | }; 16 | 17 | export type PlacedNumber = { 18 | type: typeof PLACED_NUMBER; 19 | index: number; 20 | }; 21 | 22 | export type CurrentIndex = { 23 | type: typeof CURRENT_INDEX; 24 | index: number; 25 | }; 26 | 27 | export type SwappedPair = { 28 | type: typeof SWAPPED_PAIR; 29 | firstIndex: number; 30 | secondIndex: number; 31 | }; 32 | 33 | export type FinishedVisitingIndex = { 34 | type: typeof FINISHED_VISITING_INDEX; 35 | index: number; 36 | }; 37 | 38 | export type MergePair = { 39 | type: typeof MERGE_PAIR; 40 | firstIndex: number; 41 | secondIndex: number; 42 | }; 43 | 44 | export type FinishedMergePair = { 45 | type: typeof FINISHED_MERGE_PAIR; 46 | firstIndex: number; 47 | secondIndex: number; 48 | currentIndex: number; 49 | placedNumber: number; 50 | }; 51 | 52 | export type LeftToMerge = { 53 | type: typeof LEFT_TO_MERGE; 54 | indexLeftToMerge: number; 55 | }; 56 | 57 | export type FinishedLeftToMerge = { 58 | type: typeof FINISHED_LEFT_TO_MERGE; 59 | indexLeftToMerge: number; 60 | currentIndex: number; 61 | placedNumber: number; 62 | }; 63 | 64 | export type SortingOutputElementType = 65 | | SwapPair 66 | | PlacedNumber 67 | | CurrentIndex 68 | | SwappedPair 69 | | FinishedVisitingIndex 70 | | MergePair 71 | | FinishedMergePair 72 | | LeftToMerge 73 | | FinishedLeftToMerge; 74 | 75 | export type SortingAlgorithmResult = { 76 | output: SortingOutputElementType[]; 77 | }; 78 | -------------------------------------------------------------------------------- /src/utils/types/sorting-types/sorting-stack-type.ts: -------------------------------------------------------------------------------- 1 | export const UNVISITED_STACK = 'UNVISITED_STACK'; 2 | export const SWAP_STACK = 'SWAP_STACK'; 3 | export const CURRENT_STACK = 'CURRENT_STACK'; 4 | export const PUT_IN_PLACE = 'PUT_IN_PLACE'; 5 | export const MERGING_STACK = 'MERGING_STACK'; 6 | export const LEFT_TO_MERGE_STACK = 'LEFT_TO_MERGE_STACK'; 7 | 8 | export type SortingStackType = 9 | | typeof UNVISITED_STACK 10 | | typeof SWAP_STACK 11 | | typeof CURRENT_STACK 12 | | typeof PUT_IN_PLACE 13 | | typeof MERGING_STACK 14 | | typeof LEFT_TO_MERGE_STACK; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "noEmit": true, 19 | "jsx": "react", 20 | "isolatedModules": true 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------