At its core, a pathfinding algorithm searches a graph/grid by starting at one vertex/node and exploring adjacent nodes until the destination node is reached, generally with the intent of finding the cheapest route.
All the algorithms for this application are adapted for a 2D grid, where movements from node to node have cost of 1.
Choose an algorithm from the "Algorithms" drop-down menu.
Note that some algorithms are unweighted, while others are weighted. Unweighted algorithms do not take weight nodes into account, whereas weighted ones do. Furthermore, not all algorithms guarantee the shortest path.
Not all algorithms are equal. Please read the notes down below!
A* Search is weighted and it is arguably the best pathfinding algorithm out there. It uses heuristics to guarantee the shortest path and is much faster than Dijkstra's Algorithm
Dijkstra's Algorithm is also weighted. Dijkstra's algorithm is the father of pathfinding algorithms and it guarantees the shortest path
Breath-first Search is unweighted and it's a good algorithm which guarantees the shortest path
Depth-first Search is unweighted and it's a very bad algorithm for pathfinding. Moreover, it does not guarantee the shortest path
P.S Many new algorithms are going to come out with the next version of this app!
",
60 | },
61 | },
62 | {
63 | title: 'How to move start and end point nodes',
64 | body: {
65 | __html:
66 | '
1. How to set a new start point.
1.1 In order to set new start point you simply need to hold down "Ctrl" key while clicking on the desired node.
2. How to set a new end point.
2.1 Setting an end node is the same as setting start node but instead of holding down "Ctrl" key you must hold down "Alt" key.
1.1 In order to add wall node you need to click on the desired node. Additionaly, you can hold down your left mouse button while moving over the nodes and this will also set wall nodes.
2. How to add weight node.
2.1 Adding a wall node is the same as adding weight node except we have to keep the "Shift" key pressed.
3. Removing wall and weight nodes
In order to remove wall or weight node, simply click onto the node with the same key combination you have used to add them or use the "Clear board" button.
Walls are impassable meaning that a path cannot cross through. Weights, however, are not impassable. They simply cost more to move through. If you are wondering what does that mean, imagine the following situation: It is time to go home after a long day at work and you turn on your GPS, put in your home address and the GPS will calculate the route for you. But in the meantime, there could be a traffic jam so the GPS would say: "Hey, there is a traffic jam, would you like me to take a roundabout route which will save you a lot of time by avoiding the traffic jams and you will get home much faster". In this application, moving through a weight node has a "cost" of 15.
Use the navbar buttons to visualize algorithms and clear grid!
Click on the "Visualize" button after you have chosen an algorithm in order to visualize it. Also, you can clear the current path, walls and weights from the "Clear Board" button positioned in the top right corner. Moreover, you do not need to clear the board manually after you have visualized given algorithm, simply choose the new algorithm which you want to visualize and the grid will clear itself automatically when "Visualize" button is clicked.
',
95 | },
96 | },
97 | {
98 | title: 'Thank you for coming to this stage of the tutorial.',
99 | body: {
100 | __html:
101 | '
I believe that you are ready to go on your own now and explore the different algorithms.
If for some reason you want to get back to this tutorial simply refresh the page.
By the way, if you are interested in the source code of this project you can check it out in my github account. Giving the project a star is highly appreciated!
',
102 | },
103 | },
104 | ];
105 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/constants/pathfindingAlgorithms.js:
--------------------------------------------------------------------------------
1 | export const availablePathFindingAlgorithms = [
2 | {
3 | value: 'astar',
4 | label: 'A* Search',
5 | description: 'A* Search is 𝐰𝐞𝐢𝐠𝐡𝐭𝐞𝐝 and 𝐠𝐮𝐚𝐫𝐚𝐧𝐭𝐞𝐞𝐬 the shortest path!',
6 | isWeight: true,
7 | },
8 | {
9 | value: 'dijkstra',
10 | label: "Dijkstra's Search",
11 | description:
12 | "Dijkstra's Algorithm is 𝐰𝐞𝐢𝐠𝐡𝐭𝐞𝐝 and 𝐠𝐮𝐚𝐫𝐚𝐧𝐭𝐞𝐞𝐬 the shortest path!",
13 | isWeight: true,
14 | },
15 | {
16 | value: 'bfs',
17 | label: 'Breadth-first Search',
18 | description:
19 | 'Breath-first Search is 𝐮𝐧𝐰𝐞𝐢𝐠𝐡𝐭𝐞𝐝 and 𝐠𝐮𝐚𝐫𝐚𝐧𝐭𝐞𝐞𝐬 the shortest path!',
20 | isWeight: false,
21 | },
22 | {
23 | value: 'dfs',
24 | label: 'Depth-first Search',
25 | description:
26 | 'Depth-first Search is 𝐮𝐧𝐰𝐞𝐢𝐠𝐡𝐭𝐞𝐝 and 𝐝𝐨𝐞𝐬 𝐧𝐨𝐭 𝐠𝐮𝐚𝐫𝐚𝐧𝐭𝐞𝐞 the shortest path!',
27 | isWeight: false,
28 | },
29 | ];
30 |
31 | export const MAZE_TYPES = [
32 | {
33 | label: 'Generate wall maze',
34 | value: 'wall',
35 | },
36 | {
37 | label: 'Generate weight maze',
38 | value: 'weight',
39 | },
40 | ];
41 |
42 | export const DEFAULT_ANIMATION = {
43 | label: 'Average',
44 | value: 20,
45 | };
46 |
47 | export const ANIMATION_SPEEDS = [
48 | {
49 | label: 'Slow',
50 | value: 120,
51 | },
52 | DEFAULT_ANIMATION,
53 | {
54 | label: 'Fast',
55 | value: 10,
56 | },
57 | ];
58 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/constants/sortingAlgorithmsConstants.js:
--------------------------------------------------------------------------------
1 | export const INITIALIZE_CHART_DATA = 'INITIALIZE_CHART_DATA';
2 | export const GENERATE_NEW_ARRAY = 'GENERATE_NEW_ARRAY';
3 | export const SET_IS_NAVBAR_CLICKABLE = 'SET_IS_NAVBAR_CLICKABLE';
4 |
5 | export const BAR_CHART_DEFAULT_BACKGROUND_COLOR = 'rgba(255,99,132,0.2)';
6 | export const MARKED_ELEMENT_BACKGROUND_COLOR = 'yellow';
7 |
8 | export const SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR =
9 | 'SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR';
10 |
11 | export const SET_TOTAL_SWAPS = 'SET_TOTAL_SWAPS';
12 | export const TOTAL_SWAPS_DEFAULT_VALUE = 0;
13 |
14 | export const availableSortingAlgorithms = [
15 | {
16 | value: 'mergesort',
17 | label: 'Merge sort'
18 | },
19 | {
20 | value: 'bubblesort',
21 | label: 'Bubble Sort'
22 | },
23 | {
24 | value: 'heapsort',
25 | label: 'Heap Sort'
26 | },
27 | {
28 | value: 'quicksort',
29 | label: 'Quick Sort'
30 | },
31 | {
32 | value: 'selectionsort',
33 | label: 'Selection Sort'
34 | }
35 | ];
36 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/helpers/fetchData.js:
--------------------------------------------------------------------------------
1 | import { showError } from '../store/error/actions';
2 | import { DEFAULT_ERROR_MESSAGE } from '../constants/errorConstants';
3 |
4 | export const makePostApiCallAsync = async (url, data, dispatchError) => {
5 | try {
6 | const response = await fetch(url, {
7 | method: 'POST',
8 | headers: {
9 | 'content-type': 'application/json'
10 | },
11 | body: data
12 | });
13 |
14 | const result = response.json();
15 | if (response.status <= 400) {
16 | return result;
17 | } else {
18 | dispatchError(showError([DEFAULT_ERROR_MESSAGE]));
19 | }
20 | } catch (error) {
21 | dispatchError(showError([DEFAULT_ERROR_MESSAGE]));
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/helpers/pathFindingAlgorithms/dataVisualizer.js:
--------------------------------------------------------------------------------
1 | import {
2 | SHORTEST_PATH_CLASSNAME,
3 | VISITED_NODE_CLASSNAME,
4 | } from '../../constants/gridConstants';
5 | import {
6 | setIsNavbarClickable,
7 | setWallNode,
8 | setWeightNode,
9 | setTotalNodesExplored,
10 | } from '../../store/pathFindingAlgorithms/actions';
11 |
12 | const nodeName = 'node';
13 | const mazeGenerationAnimationSpeed = 20;
14 |
15 | export const visualizeResult = (
16 | dispatch,
17 | visitedNodesInOrder,
18 | nodesInShortestPathOrder,
19 | totalNodesExplored,
20 | animationSpeed
21 | ) => {
22 | const allVisitedNodesInOrder = visitedNodesInOrder;
23 | const allNodesInShortestPathOrder = nodesInShortestPathOrder;
24 | dispatch(setIsNavbarClickable(false));
25 | animateResult(
26 | dispatch,
27 | allVisitedNodesInOrder,
28 | allNodesInShortestPathOrder,
29 | totalNodesExplored,
30 | animationSpeed
31 | );
32 | };
33 |
34 | export const visualizeMazeGeneration = (dispatch, nodes, mazeType) => {
35 | dispatch(setIsNavbarClickable(false));
36 | animateMazeGeneration(dispatch, nodes, mazeType);
37 | };
38 |
39 | const animateMazeGeneration = (dispatch, nodes, mazeType) => {
40 | for (let i = 0; i < nodes.length; i++) {
41 | const node = nodes[i];
42 | const row = node[0];
43 | const col = node[1];
44 |
45 | setTimeout(() => {
46 | if (mazeType === 'wall') {
47 | dispatch(setWallNode(row, col));
48 | }
49 | if (mazeType === 'weight') {
50 | dispatch(setWeightNode(row, col));
51 | }
52 |
53 | if (i === nodes.length - 1) {
54 | dispatch(setIsNavbarClickable(true));
55 | clearTimeout();
56 | }
57 | }, mazeGenerationAnimationSpeed * i);
58 | }
59 | };
60 |
61 | const animateResult = (
62 | dispatch,
63 | allVisitedNodesInOrder,
64 | allNodesInShortestPathOrder,
65 | totalNodesExplored,
66 | animationSpeed
67 | ) => {
68 | for (let i = 0; i <= allVisitedNodesInOrder.length; i++) {
69 | if (i === allVisitedNodesInOrder.length) {
70 | setTimeout(() => {
71 | animateShortestPath(
72 | dispatch,
73 | allNodesInShortestPathOrder,
74 | totalNodesExplored
75 | );
76 | }, animationSpeed * i);
77 | return;
78 | }
79 |
80 | setTimeout(() => {
81 | const node = allVisitedNodesInOrder[i];
82 | if (node) {
83 | const element = getElement(nodeName, node.row, node.col);
84 | if (element === null) return;
85 | element.className = VISITED_NODE_CLASSNAME;
86 | }
87 |
88 | return;
89 | }, animationSpeed * i);
90 | }
91 | };
92 |
93 | const animateShortestPath = (dispatch, nodes, totalNodesExplored) => {
94 | for (let i = 0; i <= nodes.length; i++) {
95 | setTimeout(() => {
96 | const node = nodes[i];
97 | if (node) {
98 | const element = getElement(nodeName, node.row, node.col);
99 | if (element === null) return;
100 | element.className = SHORTEST_PATH_CLASSNAME;
101 | }
102 |
103 | if (i === nodes.length - 1) {
104 | dispatch(setIsNavbarClickable(true));
105 | dispatch(setTotalNodesExplored(totalNodesExplored));
106 | clearTimeout();
107 | }
108 | }, 50 * i * 2);
109 | }
110 | };
111 |
112 | const getElement = (nodeName, row, col) =>
113 | document.getElementById(`${nodeName}-${row}-${col}`);
114 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/helpers/pathFindingAlgorithms/pathFindingHelper.js:
--------------------------------------------------------------------------------
1 | import {
2 | ROWS,
3 | COLS,
4 | START_NODE_ROW,
5 | START_NODE_COL,
6 | END_NODE_ROW,
7 | END_NODE_COL,
8 | SHORTEST_PATH_CLASSNAME,
9 | VISITED_NODE_CLASSNAME
10 | } from '../../constants/gridConstants';
11 |
12 | // Modifiable
13 | let StartNodeRow;
14 | let StartNodeCol;
15 | let EndNodeRow;
16 | let EndNodeCol;
17 |
18 | export function getInitialGrid() {
19 | const grid = [[], []];
20 | for (let row = 0; row < ROWS; row++) {
21 | grid[row] = [];
22 | for (let col = 0; col < COLS; col++) {
23 | grid[row][col] = createNode(row, col);
24 | }
25 | }
26 |
27 | StartNodeRow = START_NODE_ROW;
28 | StartNodeCol = START_NODE_COL;
29 | EndNodeRow = END_NODE_ROW;
30 | EndNodeCol = END_NODE_COL;
31 |
32 | return grid;
33 | }
34 |
35 | export const removeAllWeightNodes = grid => {
36 | if (!grid) return;
37 | for (let row = 0; row < ROWS; row++) {
38 | for (let col = 0; col < COLS; col++) {
39 | if (grid[row][col].isWeight) {
40 | grid[row][col].isWeight = false;
41 | }
42 | }
43 | }
44 | return grid;
45 | };
46 |
47 | export const clearGrid = () => {
48 | const nodeClassName = 'node';
49 | for (let row = 0; row < ROWS; row++) {
50 | for (let col = 0; col < COLS; col++) {
51 | const element = document.getElementById(`${nodeClassName}-${row}-${col}`);
52 | if (
53 | element &&
54 | (element.className === SHORTEST_PATH_CLASSNAME ||
55 | element.className === VISITED_NODE_CLASSNAME)
56 | ) {
57 | element.className = nodeClassName;
58 | }
59 | }
60 | }
61 | };
62 |
63 | const createNode = (row, col) => {
64 | return {
65 | row,
66 | col,
67 | isStart: row === START_NODE_ROW && col === START_NODE_COL,
68 | isEnd: row === END_NODE_ROW && col === END_NODE_COL,
69 | isWall: false,
70 | isWeight: false
71 | };
72 | };
73 |
74 | export const setWallNode = (grid, row, col) => {
75 | if (!isPlaceable(row, col)) return grid;
76 |
77 | const newGrid = grid.slice();
78 | const node = newGrid[row][col];
79 | const newNode = {
80 | ...node,
81 | isWall: !node.isWall,
82 | isWeight: false
83 | };
84 | newGrid[row][col] = newNode;
85 | return newGrid;
86 | };
87 |
88 | export const setWeightNode = (grid, row, col) => {
89 | if (!isPlaceable(row, col)) return grid;
90 |
91 | const newGrid = grid.slice();
92 | const node = newGrid[row][col];
93 | const newNode = {
94 | ...node,
95 | isWeight: !node.isWeight,
96 | isWall: false
97 | };
98 | newGrid[row][col] = newNode;
99 | return newGrid;
100 | };
101 |
102 | export const setStartNode = (grid, row, col) => {
103 | if (!isPlaceable(row, col)) {
104 | return grid;
105 | }
106 |
107 | const newGrid = grid.slice();
108 | newGrid[StartNodeRow][StartNodeCol].isStart = false;
109 | newGrid[row][col].isStart = true;
110 | newGrid[row][col].isWall = false;
111 | newGrid[row][col].isWeight = false;
112 |
113 | StartNodeRow = row;
114 | StartNodeCol = col;
115 |
116 | return newGrid;
117 | };
118 |
119 | export const setEndNode = (grid, row, col) => {
120 | if (!isPlaceable(row, col)) {
121 | return grid;
122 | }
123 |
124 | const newGrid = grid.slice();
125 | newGrid[EndNodeRow][EndNodeCol].isEnd = false;
126 | newGrid[row][col].isEnd = true;
127 | newGrid[row][col].isWall = false;
128 | newGrid[row][col].isWeight = false;
129 |
130 | EndNodeRow = row;
131 | EndNodeCol = col;
132 |
133 | return newGrid;
134 | };
135 |
136 | const isPlaceable = (row, col) => {
137 | if (
138 | (row === StartNodeRow && col === StartNodeCol) ||
139 | (row === EndNodeRow && col === EndNodeCol)
140 | ) {
141 | return false;
142 | }
143 |
144 | return true;
145 | };
146 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/helpers/sortingAlgorithmsHelper.js:
--------------------------------------------------------------------------------
1 | import {
2 | BAR_CHART_DEFAULT_BACKGROUND_COLOR,
3 | MARKED_ELEMENT_BACKGROUND_COLOR
4 | } from '../constants/sortingAlgorithmsConstants';
5 | import {
6 | setBarChartElementBackgroundColor,
7 | setIsNavbarClickable
8 | } from '../store/sortingAlgorithms/actions';
9 |
10 | const arraySize = 20;
11 | const randomMultiplyingFactor = 300;
12 |
13 | export const getInitialChart = () => {
14 | const barChart = {
15 | labels: [],
16 | dataset: {
17 | backgroundColor: [],
18 | data: []
19 | }
20 | };
21 |
22 | const dataSet = barChart.dataset;
23 | for (let i = 0; i <= arraySize; i++) {
24 | const number = Math.floor(Math.random() * randomMultiplyingFactor);
25 | dataSet.backgroundColor.push(BAR_CHART_DEFAULT_BACKGROUND_COLOR);
26 | barChart.labels.push(number);
27 | dataSet.data.push(number);
28 | }
29 |
30 | return barChart;
31 | };
32 |
33 | export const generateNewChart = data => {
34 | const dataSet = data.datasets[0];
35 | for (let i = 0; i <= arraySize; i++) {
36 | const number = Math.floor(Math.random() * randomMultiplyingFactor);
37 | data.labels[i] = number;
38 | dataSet.data[i] = number;
39 | }
40 |
41 | return data;
42 | };
43 |
44 | export const setBackgroundColorToChartElements = (
45 | barChart,
46 | elements,
47 | color
48 | ) => {
49 | const dataSet = barChart.datasets[0];
50 | for (let i = 0; i < elements.length; i++) {
51 | dataSet.backgroundColor[elements[i]] = color;
52 | }
53 | return barChart.backgroundColor;
54 | };
55 |
56 | export const visualizeArrayElementsSwapping = async (
57 | dispatch,
58 | barChart,
59 | swappingIndexes
60 | ) => {
61 | const timeOutMs = 500;
62 |
63 | dispatch(setIsNavbarClickable(false));
64 |
65 | for (let i = 0; i < swappingIndexes.length; i++) {
66 | const element = swappingIndexes[i];
67 | const firstIndex = element[0];
68 | const secondIndex = element[1];
69 |
70 | await Promise.all([
71 | dispatch(
72 | setBarChartElementBackgroundColor(
73 | [firstIndex, secondIndex],
74 | MARKED_ELEMENT_BACKGROUND_COLOR
75 | )
76 | ),
77 | timeout(timeOutMs)
78 | ]);
79 |
80 | await Promise.all([
81 | swapElements(barChart, firstIndex, secondIndex),
82 | timeout(250)
83 | ]);
84 |
85 | await Promise.all([
86 | dispatch(
87 | setBarChartElementBackgroundColor(
88 | [firstIndex, secondIndex],
89 | BAR_CHART_DEFAULT_BACKGROUND_COLOR
90 | )
91 | ),
92 | timeout(timeOutMs)
93 | ]);
94 |
95 | if (i === swappingIndexes.length - 1) {
96 | dispatch(setIsNavbarClickable(true));
97 | }
98 | }
99 | };
100 |
101 | const timeout = ms => {
102 | return new Promise(resolve => setTimeout(resolve, ms));
103 | };
104 |
105 | const swapElements = (barChart, firstIndex, secondIndex) => {
106 | const dataSet = barChart.datasets[0];
107 |
108 | const temp = barChart.labels[firstIndex];
109 |
110 | const secondElement = barChart.labels[secondIndex];
111 | barChart.labels[firstIndex] = secondElement;
112 | dataSet.data[firstIndex] = secondElement;
113 |
114 | barChart.labels[secondIndex] = temp;
115 | dataSet.data[secondIndex] = temp;
116 | };
117 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/index.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.css';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import App from './App';
6 | import registerServiceWorker from './registerServiceWorker';
7 |
8 | const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href');
9 | const rootElement = document.getElementById('root');
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | rootElement
16 | );
17 |
18 | registerServiceWorker();
19 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register () {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 | } else {
39 | // Is not local host. Just register service worker
40 | registerValidSW(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW (swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker (swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister () {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/error/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | SHOW_ERROR_WITH_MESSAGE,
3 | CLEAR_ERRORS
4 | } from '../../../constants/errorConstants';
5 |
6 | export const showError = messages => {
7 | return {
8 | type: SHOW_ERROR_WITH_MESSAGE,
9 | payload: messages
10 | };
11 | };
12 |
13 | export const clearErrors = () => {
14 | return {
15 | type: CLEAR_ERRORS
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/error/context/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 | import errorReducer from '../reducers';
3 |
4 | export const initialState = {
5 | messages: [],
6 | showError: false
7 | };
8 |
9 | export const ErrorContext = createContext();
10 |
11 | export const ErrorProvider = props => {
12 | const [state, dispatchError] = useReducer(errorReducer, initialState);
13 | return (
14 |
15 | {props.children}
16 |
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/error/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | SHOW_ERROR_WITH_MESSAGE,
3 | CLEAR_ERRORS
4 | } from '../../../constants/errorConstants';
5 | import { initialState } from '../context';
6 |
7 | const errorReducer = (state = initialState, action) => {
8 | switch (action.type) {
9 | case SHOW_ERROR_WITH_MESSAGE:
10 | return {
11 | ...state,
12 | showError: true,
13 | messages: action.payload
14 | };
15 | case CLEAR_ERRORS:
16 | state = initialState;
17 | return state;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default errorReducer;
24 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/pathFindingAlgorithms/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | INITIALIZE_GRID,
3 | SET_IS_NAVBAR_CLICKABLE,
4 | SET_START_NODE,
5 | SET_END_NODE,
6 | SET_WEIGHT_NODE,
7 | SET_WALL_NODE,
8 | SET_ALGORITHM,
9 | SET_ALGORITHM_DESCRIPTION,
10 | CLEAR_STATE,
11 | REMOVE_WEIGHT_NODES,
12 | CLEAR_GRID,
13 | SET_TOTAL_NODES_EXPLORED
14 | } from '../../../constants/gridConstants';
15 |
16 | export const initializeGrid = () => {
17 | return {
18 | type: INITIALIZE_GRID
19 | };
20 | };
21 |
22 | export const setIsNavbarClickable = isClickable => {
23 | return {
24 | type: SET_IS_NAVBAR_CLICKABLE,
25 | payload: isClickable
26 | };
27 | };
28 |
29 | export const setStartNode = (row, col) => {
30 | return {
31 | type: SET_START_NODE,
32 | payload: { row, col }
33 | };
34 | };
35 |
36 | export const setEndNode = (row, col) => {
37 | return {
38 | type: SET_END_NODE,
39 | payload: { row, col }
40 | };
41 | };
42 |
43 | export const setWeightNode = (row, col) => {
44 | return {
45 | type: SET_WEIGHT_NODE,
46 | payload: { row, col }
47 | };
48 | };
49 |
50 | export const setWallNode = (row, col) => {
51 | return {
52 | type: SET_WALL_NODE,
53 | payload: { row, col }
54 | };
55 | };
56 |
57 | export const setTotalNodesExplored = value => {
58 | return {
59 | type: SET_TOTAL_NODES_EXPLORED,
60 | payload: value
61 | };
62 | };
63 |
64 | export const setAlgorithm = algorithm => {
65 | return {
66 | type: SET_ALGORITHM,
67 | payload: algorithm
68 | };
69 | };
70 |
71 | export const setAlgorithmDescription = description => {
72 | return {
73 | type: SET_ALGORITHM_DESCRIPTION,
74 | payload: description
75 | };
76 | };
77 |
78 | export const clearState = () => {
79 | return {
80 | type: CLEAR_STATE
81 | };
82 | };
83 |
84 | export const clearGrid = () => {
85 | return {
86 | type: CLEAR_GRID
87 | };
88 | };
89 |
90 | export const removeWeightNodes = () => {
91 | return {
92 | type: REMOVE_WEIGHT_NODES
93 | };
94 | };
95 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/pathFindingAlgorithms/context/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 | import pathFindingAlgorithmsReducer from '../reducers';
3 | import {
4 | START_NODE_ROW,
5 | START_NODE_COL,
6 | END_NODE_ROW,
7 | END_NODE_COL,
8 | TOTAL_NODES_EXPLORED_DEFAULT_VALUE
9 | } from '../../../constants/gridConstants';
10 | import { availablePathFindingAlgorithms } from '../../../constants/pathfindingAlgorithms';
11 |
12 | export const initialState = {
13 | isLoading: true,
14 | isNavbarClickable: true,
15 | grid: [],
16 | totalNodesExplored: TOTAL_NODES_EXPLORED_DEFAULT_VALUE,
17 | algorithms: availablePathFindingAlgorithms,
18 | algorithm: '',
19 | algorithmDescription: '',
20 | isWeightNodeAllowed: true,
21 | startNode: { row: START_NODE_ROW, col: START_NODE_COL, isStart: true },
22 | endNode: { row: END_NODE_ROW, col: END_NODE_COL, isEnd: true }
23 | };
24 |
25 | export const PathFindingAlgorithmsContext = createContext();
26 |
27 | export const PathFindingAlgorithmsProvider = props => {
28 | const [state, dispatch] = useReducer(
29 | pathFindingAlgorithmsReducer,
30 | initialState
31 | );
32 | return (
33 |
34 | {props.children}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/pathFindingAlgorithms/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | INITIALIZE_GRID,
3 | SET_START_NODE,
4 | SET_END_NODE,
5 | SET_WEIGHT_NODE,
6 | SET_WALL_NODE,
7 | SET_ALGORITHM,
8 | SET_ALGORITHM_DESCRIPTION,
9 | CLEAR_STATE,
10 | CLEAR_GRID,
11 | REMOVE_WEIGHT_NODES,
12 | SET_IS_NAVBAR_CLICKABLE,
13 | SET_TOTAL_NODES_EXPLORED
14 | } from '../../../constants/gridConstants';
15 |
16 | import {
17 | getInitialGrid,
18 | setStartNode,
19 | setEndNode,
20 | setWeightNode,
21 | setWallNode,
22 | removeAllWeightNodes,
23 | clearGrid
24 | } from '../../../helpers/pathFindingAlgorithms/pathFindingHelper';
25 | import { initialState } from '../context';
26 |
27 | let isGridChanged = false;
28 |
29 | const pathFindingAlgorithmsReducer = (state = initialState, action) => {
30 | switch (action.type) {
31 | case INITIALIZE_GRID:
32 | return { ...state, grid: getInitialGrid(), isLoading: false };
33 | case SET_IS_NAVBAR_CLICKABLE:
34 | return { ...state, isNavbarClickable: action.payload };
35 | case SET_START_NODE:
36 | MarkGridAsChanged();
37 |
38 | return {
39 | ...state,
40 | grid: setStartNode(state.grid, action.payload.row, action.payload.col),
41 | startNode: {
42 | row: action.payload.row,
43 | col: action.payload.col,
44 | isStart: true
45 | }
46 | };
47 | case SET_END_NODE:
48 | MarkGridAsChanged();
49 |
50 | return {
51 | ...state,
52 | grid: setEndNode(state.grid, action.payload.row, action.payload.col),
53 | endNode: {
54 | row: action.payload.row,
55 | col: action.payload.col,
56 | isEnd: false
57 | }
58 | };
59 | case SET_WEIGHT_NODE:
60 | MarkGridAsChanged();
61 |
62 | return {
63 | ...state,
64 | grid: setWeightNode(state.grid, action.payload.row, action.payload.col)
65 | };
66 | case SET_WALL_NODE:
67 | MarkGridAsChanged();
68 |
69 | return {
70 | ...state,
71 | grid: setWallNode(state.grid, action.payload.row, action.payload.col)
72 | };
73 | case SET_TOTAL_NODES_EXPLORED:
74 | return {
75 | ...state,
76 | totalNodesExplored: action.payload
77 | };
78 | case SET_ALGORITHM:
79 | const algorithm = state.algorithms.find(
80 | el => el.value === action.payload
81 | );
82 | if (!algorithm || state.algorithm === action.payload) return state;
83 |
84 | return {
85 | ...state,
86 | algorithm: action.payload,
87 | isWeightNodeAllowed: algorithm.isWeight
88 | };
89 | case SET_ALGORITHM_DESCRIPTION:
90 | if (!action.payload || state.algorithm === action.payload) return state;
91 | return { ...state, algorithmDescription: action.payload };
92 | case CLEAR_STATE:
93 | if (!isGridChanged) return state;
94 |
95 | state = initialState;
96 | isGridChanged = false;
97 | clearGrid();
98 |
99 | return {
100 | ...state,
101 | grid: getInitialGrid(),
102 | isLoading: false
103 | };
104 | case CLEAR_GRID:
105 | if (state.grid.length === 0) return state;
106 | MarkGridAsChanged();
107 | clearGrid();
108 | return state;
109 | case REMOVE_WEIGHT_NODES:
110 | let newGrid = removeAllWeightNodes(state.grid);
111 | return {
112 | ...state,
113 | grid: newGrid
114 | };
115 | default:
116 | return state;
117 | }
118 |
119 | function MarkGridAsChanged() {
120 | if (!isGridChanged) {
121 | isGridChanged = true;
122 | }
123 | }
124 | };
125 |
126 | export default pathFindingAlgorithmsReducer;
127 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/sortingAlgorithms/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | INITIALIZE_CHART_DATA,
3 | GENERATE_NEW_ARRAY,
4 | SET_IS_NAVBAR_CLICKABLE,
5 | SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR,
6 | SET_TOTAL_SWAPS
7 | } from '../../../constants/sortingAlgorithmsConstants';
8 |
9 | export const initializeChartData = () => {
10 | return {
11 | type: INITIALIZE_CHART_DATA
12 | };
13 | };
14 |
15 | export const generateNewArray = () => {
16 | return {
17 | type: GENERATE_NEW_ARRAY
18 | };
19 | };
20 |
21 | export const setIsNavbarClickable = isClickable => {
22 | return {
23 | type: SET_IS_NAVBAR_CLICKABLE,
24 | payload: isClickable
25 | };
26 | };
27 |
28 | export const setTotalSwaps = value => {
29 | return {
30 | type: SET_TOTAL_SWAPS,
31 | payload: value
32 | };
33 | };
34 |
35 | export const setBarChartElementBackgroundColor = (elementsIndexes, color) => {
36 | return {
37 | type: SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR,
38 | payload: { elementsIndexes, color }
39 | };
40 | };
41 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/sortingAlgorithms/context/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 | import sortingAlgorithmsReducer from '../reducers';
3 | import {
4 | TOTAL_SWAPS_DEFAULT_VALUE,
5 | availableSortingAlgorithms
6 | } from '../../../constants/sortingAlgorithmsConstants';
7 |
8 | export const initialState = {
9 | isLoading: true,
10 | isNavbarClickable: true,
11 | algorithms: availableSortingAlgorithms,
12 | totalSwaps: TOTAL_SWAPS_DEFAULT_VALUE,
13 | barChart: {
14 | labels: [],
15 | datasets: [
16 | {
17 | label: 'Value',
18 | backgroundColor: [],
19 | borderColor: 'rgba(255,99,132,1)',
20 | borderWidth: 1,
21 | hoverBackgroundColor: 'rgba(255,99,132,0.4)',
22 | hoverBorderColor: 'rgba(255,99,132,1)',
23 | data: []
24 | }
25 | ]
26 | }
27 | };
28 |
29 | export const SortingAlgorithmsContext = createContext();
30 |
31 | export const SortingAlgorithmsProvider = props => {
32 | const [state, dispatch] = useReducer(sortingAlgorithmsReducer, initialState);
33 | return (
34 |
35 | {props.children}
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/AlgoVisualizer/Web/ClientApp/src/store/sortingAlgorithms/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { initialState } from '../context';
2 | import {
3 | INITIALIZE_CHART_DATA,
4 | GENERATE_NEW_ARRAY,
5 | SET_IS_NAVBAR_CLICKABLE,
6 | SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR,
7 | SET_TOTAL_SWAPS
8 | } from '../../../constants/sortingAlgorithmsConstants';
9 | import {
10 | getInitialChart,
11 | generateNewChart,
12 | setBackgroundColorToChartElements
13 | } from '../../../helpers/sortingAlgorithmsHelper';
14 |
15 | const sortingAlgorithmsReducer = (state = initialState, action) => {
16 | switch (action.type) {
17 | case INITIALIZE_CHART_DATA:
18 | const newChart = getInitialChart();
19 | const oldDataSet = state.barChart.datasets[0];
20 | return {
21 | ...state,
22 | barChart: {
23 | ...state.barChart,
24 | labels: newChart.labels,
25 | datasets: [
26 | {
27 | ...oldDataSet,
28 | data: newChart.dataset.data,
29 | backgroundColor: newChart.dataset.backgroundColor
30 | }
31 | ]
32 | },
33 | isLoading: false
34 | };
35 | case GENERATE_NEW_ARRAY:
36 | const data = state.barChart;
37 | return {
38 | ...state,
39 | barChart: Object.assign({}, data, {
40 | data: generateNewChart(data)
41 | })
42 | };
43 | case SET_IS_NAVBAR_CLICKABLE:
44 | return {
45 | ...state,
46 | isNavbarClickable: action.payload
47 | };
48 | case SET_TOTAL_SWAPS:
49 | return { ...state, totalSwaps: action.payload };
50 | case SET_BAR_CHART_ELEMENT_BACKGROUND_COLOR:
51 | const barChart = state.barChart;
52 | return {
53 | ...state,
54 | barChart: Object.assign({}, barChart, {
55 | backgroundColor: Object.assign({}, barChart.backgroundColor, {
56 | backgroundColor: setBackgroundColorToChartElements(
57 | barChart,
58 | action.payload.elementsIndexes,
59 | action.payload.color
60 | )
61 | })
62 | })
63 | };
64 | default:
65 | return state;
66 | }
67 | };
68 |
69 | export default sortingAlgorithmsReducer;
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Melik Pehlivanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AlgorithmVisualizer
2 |
3 | **AlgorithmVisualizer** is an open-source web project which visualizes different pathfinding and sorting algorithms. I hope that you will enjoy this project just as much as I enjoyed building it. If you find any bugs, feel free to open an issue or make a pull request. https://melikpehlivanov.github.io/AlgorithmVisualizer/
4 |
5 | >**My initial idea was to learn algorithms and react in the meantime so I decided to build this project. Firstly, I used react-redux and then switched to react-hooks in order to practice React. If you're interested to see the two different approaches, check the repository branches. By the way, the algorithmic part could have been implemented on the front end in order to eliminate the 2x more work but I wanted the algorithms to be written in C#. I also could use event sourcing but it would've been overengineering for such a small project.**
6 |
7 | [](https://dev.azure.com/melikpehlivanov/AlgoVisualizer/_build/latest?definitionId=7)
8 |
9 | ## Avalailable algorithms
10 |
11 | ***
12 |
13 | **Pathfinding algorithms:**
14 | * ___A* Search___ is weighted and it is arguably the best pathfinding algorithm out there. It uses heuristics to guarantee the shortest path and is much faster than Dijkstra's Algorithm
15 | * **Dijkstra's** Algorithm is also weighted. Dijkstra's algorithm is the father of pathfinding algorithms and it guarantees the shortest path
16 | * **Breath-first Search** is unweighted and it's a good algorithm which guarantees the shortest path
17 | * **Depth-first Search** is unweighted and it's a very bad algorithm for pathfinding. Moreover, it does not guarantee the shortest path
18 | ***
19 |
20 | **Sorting algorithms:**
21 | * Merge sort
22 | * Buble sort
23 | * Heap sort
24 | * Quick sort
25 |
26 | P.S Many more algorithms are going to come out with the next version of this application. By the way, if you like the project feel free to give it a star.
27 |
--------------------------------------------------------------------------------