├── .gitignore
├── app-demo.gif
├── src
├── utils
│ ├── generateRandomNumber.js
│ ├── swap.js
│ ├── getNumberOfAlgosInRow.js
│ ├── appendChildrenToHtmlElement.js
│ ├── wait.js
│ ├── generateRandomArray.js
│ └── createHtmlArrayElements.js
├── consts.js
├── sortingAlgos
│ ├── bubbleSort.js
│ ├── insertionSort.js
│ ├── mergeSort.js
│ ├── quickSort.js
│ └── heapSort.js
└── index.js
├── package.json
├── README.md
├── yarn.lock
├── style.css
├── LICENSE
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | dist
4 |
--------------------------------------------------------------------------------
/app-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loonywizard/sorting-algos-visualizer/HEAD/app-demo.gif
--------------------------------------------------------------------------------
/src/utils/generateRandomNumber.js:
--------------------------------------------------------------------------------
1 | /*
2 | * generates random number between min and max
3 | */
4 | function generateRandomNumber(min, max) {
5 | return Math.round(Math.random() * (max - min) + min)
6 | }
7 |
8 | export { generateRandomNumber }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Vladimir Nikitin vladimirnikitin.com",
3 | "scripts": {
4 | "build": "esbuild src/index.js --bundle --minify --target=chrome58 --outfile=dist/index.js"
5 | },
6 | "devDependencies": {
7 | "esbuild": "^0.12.8"
8 | },
9 | "license": "MIT"
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/swap.js:
--------------------------------------------------------------------------------
1 | /*
2 | * swaps two given elements in a given array
3 | */
4 | function swap(arr, firstElementIndex, secondElementIndex) {
5 | const t = arr[firstElementIndex]
6 | arr[firstElementIndex] = arr[secondElementIndex]
7 | arr[secondElementIndex] = t
8 | }
9 |
10 | export { swap }
11 |
--------------------------------------------------------------------------------
/src/utils/getNumberOfAlgosInRow.js:
--------------------------------------------------------------------------------
1 | // these breakpoints are also declared in styles.css file
2 | function getNumberOfAlgosInRow() {
3 | if (document.body.offsetWidth <= '900') return 1
4 | if (document.body.offsetWidth <= '1400') return 2
5 |
6 | return 3
7 | }
8 |
9 | export { getNumberOfAlgosInRow }
10 |
--------------------------------------------------------------------------------
/src/utils/appendChildrenToHtmlElement.js:
--------------------------------------------------------------------------------
1 | /*
2 | * appends child elements to parent element
3 | */
4 | function appendChildrenToHtmlElement(htmlElement, children) {
5 | children.forEach((elementToAppend) => {
6 | htmlElement.appendChild(elementToAppend)
7 | })
8 | }
9 |
10 | export { appendChildrenToHtmlElement }
11 |
--------------------------------------------------------------------------------
/src/utils/wait.js:
--------------------------------------------------------------------------------
1 | import { OPERATIONS_PER_SECOND } from '../consts'
2 |
3 |
4 | /*
5 | * wait a given time (in milliseconds)
6 | */
7 | function wait(timeToWait = 1000 / OPERATIONS_PER_SECOND) {
8 | return new Promise((resolve) => {
9 | setTimeout(() => resolve(), timeToWait)
10 | })
11 | }
12 |
13 | export { wait }
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # sorting-algos-visualizer
4 |
5 | Visualization of common sorting algorithms
6 |
7 | Implemented sorting algorithms:
8 | - Quick sort
9 | - Merge sort
10 | - Heap sort
11 | - Bubble sort
12 | - Insertion sort
13 |
14 | live demo: https://loonywizard.github.io/sorting-algos-visualizer/
15 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | esbuild@^0.12.8:
6 | version "0.12.8"
7 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.8.tgz#ac90da77cb3bfbf49ab815200bcef7ffe1a3348f"
8 | integrity sha512-sx/LwlP/SWTGsd9G4RlOPrXnIihAJ2xwBUmzoqe2nWwbXORMQWtAGNJNYLBJJqa3e9PWvVzxdrtyFZJcr7D87g==
9 |
--------------------------------------------------------------------------------
/src/utils/generateRandomArray.js:
--------------------------------------------------------------------------------
1 | import { generateRandomNumber } from './generateRandomNumber'
2 |
3 |
4 | /*
5 | * generates array of length n of random numbers between min and max
6 | */
7 | function generateRandomArray({ n, min, max }) {
8 | const arr = []
9 |
10 | for (let i = 0; i < n; i++) {
11 | arr.push(generateRandomNumber(min, max))
12 | }
13 |
14 | return arr
15 | }
16 |
17 | export { generateRandomArray }
18 |
--------------------------------------------------------------------------------
/src/consts.js:
--------------------------------------------------------------------------------
1 | /*
2 | * declare constants
3 | */
4 | const OPERATIONS_PER_SECOND = 20
5 |
6 | const LEFT_ITEM_CSS_CLASS = 'left-item'
7 | const SAME_ITEM_CSS_CLASS = 'same-item'
8 | const RIGHT_ITEM_CSS_CLASS = 'right-item'
9 |
10 | const ARRAY_ITEM_WIDTH = 10
11 |
12 | export {
13 | OPERATIONS_PER_SECOND,
14 |
15 | LEFT_ITEM_CSS_CLASS,
16 | SAME_ITEM_CSS_CLASS,
17 | RIGHT_ITEM_CSS_CLASS,
18 |
19 | ARRAY_ITEM_WIDTH,
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/createHtmlArrayElements.js:
--------------------------------------------------------------------------------
1 | import { appendChildrenToHtmlElement } from './appendChildrenToHtmlElement'
2 |
3 |
4 | /*
5 | * creates HTML element - item of sorting array
6 | */
7 | function createArrayHtmlElement(value) {
8 | const htmlElement = document.createElement('div')
9 |
10 | htmlElement.classList.add('item')
11 | htmlElement.style.height = `${value}px`
12 |
13 | return htmlElement
14 | }
15 |
16 |
17 | /*
18 | * inits HTML elements for given array and iserts them to DOM
19 | */
20 | function createHtmlArrayElements(arr, wrapperId) {
21 | const algoWrapper = document.getElementById(wrapperId)
22 | const algoContainer = algoWrapper.getElementsByClassName('algo-container')[0]
23 |
24 | const elements = arr.map(createArrayHtmlElement)
25 |
26 | appendChildrenToHtmlElement(algoContainer, elements)
27 |
28 | return elements
29 | }
30 |
31 | export { createHtmlArrayElements }
32 |
--------------------------------------------------------------------------------
/src/sortingAlgos/bubbleSort.js:
--------------------------------------------------------------------------------
1 | import { wait } from '../utils/wait'
2 | import { swap } from '../utils/swap'
3 | import { LEFT_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts'
4 |
5 |
6 | /*
7 | * bubble sort visualization
8 | */
9 | async function bubbleSort(arr, htmlElements) {
10 | const n = arr.length
11 |
12 | for (let i = 0; i < n; i++) {
13 | for (let j = 0; j < n - i - 1; j++) {
14 | htmlElements[j].classList.add(LEFT_ITEM_CSS_CLASS)
15 | htmlElements[j + 1].classList.add(RIGHT_ITEM_CSS_CLASS)
16 |
17 |
18 | await wait()
19 |
20 | if (arr[j] > arr[j + 1]) {
21 | swap(arr, j, j + 1)
22 |
23 | htmlElements[j].style.height = `${arr[j]}px`
24 | htmlElements[j + 1].style.height = `${arr[j + 1]}px`
25 | }
26 |
27 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS)
28 | htmlElements[j + 1].classList.remove(RIGHT_ITEM_CSS_CLASS)
29 | }
30 | }
31 | }
32 |
33 | export { bubbleSort }
34 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | padding: 0;
3 | margin: 0;
4 | background: #03045e;
5 | color: #caf0f8;
6 | }
7 | a {
8 | color: #ade8f4;
9 | }
10 | .info {
11 | padding: 25px;
12 | }
13 | .wrapper {
14 | padding: 10px;
15 | display: grid;
16 | grid-template-columns: 1fr 1fr 1fr;
17 | grid-row-gap: 35px;
18 | grid-column-gap: 20px;
19 | }
20 |
21 | @media screen and (max-width: 1400px) {
22 | .wrapper {
23 | grid-template-columns: 1fr 1fr;
24 | }
25 | }
26 |
27 | @media screen and (max-width: 900px) {
28 | .wrapper {
29 | grid-template-columns: 1fr;
30 | }
31 | }
32 |
33 | @media screen and (max-width: 600px) {
34 | h1 {
35 | font-size: 1.4em;
36 | }
37 | }
38 |
39 | .algo-container {
40 | height: 400px;
41 | display: flex;
42 | align-items: flex-end;
43 | }
44 | .algo-info {
45 | margin-top: 10px;
46 | }
47 | .item {
48 | margin-left: 1px;
49 | flex-grow: 1;
50 | background: #0077b6;
51 | }
52 |
53 | .left-item {
54 | background: #90e0ef;
55 | }
56 | .right-item {
57 | background: #00b4d8;
58 | }
59 | .same-item {
60 | background: #caf0f8;
61 | }
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Vladimir Nikitin
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 |
--------------------------------------------------------------------------------
/src/sortingAlgos/insertionSort.js:
--------------------------------------------------------------------------------
1 | import { wait } from '../utils/wait'
2 | import { swap } from '../utils/swap'
3 | import { LEFT_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts'
4 |
5 |
6 | /*
7 | * insertion sort implementation
8 | */
9 | async function insertionSort(arr, htmlElements) {
10 | const n = arr.length
11 |
12 | for (let i = 1; i < n; i++) {
13 | let currentElementIndex = i
14 |
15 | htmlElements[currentElementIndex].classList.add(RIGHT_ITEM_CSS_CLASS)
16 | await wait()
17 |
18 | for (let j = i - 1; j >= 0; j--) {
19 | htmlElements[j].classList.add(LEFT_ITEM_CSS_CLASS)
20 | await wait()
21 |
22 | if (arr[currentElementIndex] >= arr[j]) {
23 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS)
24 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS)
25 | await wait()
26 | break
27 | }
28 |
29 | swap(arr, currentElementIndex, j)
30 |
31 | htmlElements[currentElementIndex].style.height = `${arr[currentElementIndex]}px`
32 | htmlElements[j].style.height = `${arr[j]}px`
33 |
34 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS)
35 | htmlElements[j].classList.remove(LEFT_ITEM_CSS_CLASS)
36 | htmlElements[j].classList.add(RIGHT_ITEM_CSS_CLASS)
37 |
38 | await wait()
39 |
40 |
41 | currentElementIndex--
42 | }
43 |
44 | htmlElements[currentElementIndex].classList.remove(LEFT_ITEM_CSS_CLASS)
45 | htmlElements[currentElementIndex].classList.remove(RIGHT_ITEM_CSS_CLASS)
46 | }
47 | }
48 |
49 | export { insertionSort }
50 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { generateRandomArray } from './utils/generateRandomArray'
2 | import { createHtmlArrayElements } from './utils/createHtmlArrayElements'
3 | import { getNumberOfAlgosInRow } from './utils/getNumberOfAlgosInRow'
4 |
5 | import { quickSort } from './sortingAlgos/quickSort'
6 | import { mergeSort } from './sortingAlgos/mergeSort'
7 | import { heapSort } from './sortingAlgos/heapSort'
8 | import { bubbleSort } from './sortingAlgos/bubbleSort'
9 | import { insertionSort } from './sortingAlgos/insertionSort'
10 |
11 | import { ARRAY_ITEM_WIDTH } from './consts'
12 |
13 |
14 | /*
15 | * init array and html elements
16 | */
17 | const ARRAY_LENGTH = Math.round(document.body.offsetWidth / (ARRAY_ITEM_WIDTH * getNumberOfAlgosInRow()))
18 | const initialRandomArray = generateRandomArray({ n: ARRAY_LENGTH, min: 10, max: 400 })
19 |
20 | const htmlElementsQuickSort = createHtmlArrayElements(initialRandomArray, 'quick-sort')
21 | const htmlElementsMergeSort = createHtmlArrayElements(initialRandomArray, 'merge-sort')
22 | const htmlElementsHeapSort = createHtmlArrayElements(initialRandomArray, 'heap-sort')
23 | const htmlElementsBubbleSort = createHtmlArrayElements(initialRandomArray, 'bubble-sort')
24 | const htmlElementsInsertionSort = createHtmlArrayElements(initialRandomArray, 'insertion-sort')
25 |
26 |
27 | /*
28 | * start visualization process
29 | */
30 | quickSort([...initialRandomArray], htmlElementsQuickSort)
31 | mergeSort([...initialRandomArray], htmlElementsMergeSort)
32 | heapSort([...initialRandomArray], htmlElementsHeapSort)
33 | bubbleSort([...initialRandomArray], htmlElementsBubbleSort)
34 | insertionSort([...initialRandomArray], htmlElementsInsertionSort)
35 |
--------------------------------------------------------------------------------
/src/sortingAlgos/mergeSort.js:
--------------------------------------------------------------------------------
1 | import { wait } from '../utils/wait'
2 | import { LEFT_ITEM_CSS_CLASS, SAME_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts'
3 |
4 |
5 | /*
6 | * merge sort visualization
7 | */
8 | async function mergeSort(arr, htmlElements, shiftIndex = 0) {
9 | if (arr.length < 2) return arr
10 |
11 | const middleElementIndex = Math.round(arr.length / 2)
12 |
13 | htmlElements[middleElementIndex + shiftIndex].classList.add(SAME_ITEM_CSS_CLASS)
14 | await wait()
15 | htmlElements[middleElementIndex + shiftIndex].classList.remove(SAME_ITEM_CSS_CLASS)
16 |
17 | const leftElements = arr.slice(0, middleElementIndex)
18 | const rightElements = arr.slice(middleElementIndex)
19 |
20 | const sortedLeftElements = await mergeSort(leftElements, htmlElements, shiftIndex)
21 | const sortedRightElements = await mergeSort(rightElements, htmlElements, shiftIndex + middleElementIndex)
22 |
23 | let i = 0, j = 0, k = 0
24 |
25 | while (i < sortedLeftElements.length && j < sortedRightElements.length) {
26 | if (sortedLeftElements[i] < sortedRightElements[j]) {
27 | arr[k] = sortedLeftElements[i]
28 | htmlElements[k + shiftIndex].classList.add(LEFT_ITEM_CSS_CLASS)
29 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px`
30 | k++
31 | i++
32 | } else {
33 | arr[k] = sortedRightElements[j]
34 | htmlElements[k + shiftIndex].classList.add(RIGHT_ITEM_CSS_CLASS)
35 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px`
36 | k++
37 | j++
38 | }
39 | await wait()
40 | }
41 |
42 | while (i < sortedLeftElements.length) {
43 | arr[k] = sortedLeftElements[i]
44 | htmlElements[k + shiftIndex].classList.add(LEFT_ITEM_CSS_CLASS)
45 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px`
46 | k++
47 | i++
48 | await wait()
49 | }
50 | while (j < sortedRightElements.length) {
51 | arr[k] = sortedRightElements[j]
52 | htmlElements[k + shiftIndex].classList.add(RIGHT_ITEM_CSS_CLASS)
53 | htmlElements[k + shiftIndex].style.height = `${arr[k]}px`
54 | k++
55 | j++
56 | await wait()
57 | }
58 |
59 | for (let i = 0; i < arr.length; i++) {
60 | htmlElements[i + shiftIndex].classList.remove(LEFT_ITEM_CSS_CLASS)
61 | htmlElements[i + shiftIndex].classList.remove(SAME_ITEM_CSS_CLASS)
62 | htmlElements[i + shiftIndex].classList.remove(RIGHT_ITEM_CSS_CLASS)
63 | }
64 |
65 | return arr
66 | }
67 |
68 | export { mergeSort }
69 |
--------------------------------------------------------------------------------
/src/sortingAlgos/quickSort.js:
--------------------------------------------------------------------------------
1 | import { wait } from '../utils/wait'
2 | import { swap } from '../utils/swap'
3 | import { LEFT_ITEM_CSS_CLASS, SAME_ITEM_CSS_CLASS, RIGHT_ITEM_CSS_CLASS } from '../consts'
4 |
5 |
6 | /*
7 | * partition function for quick sort in-place visualization
8 | *
9 | * reference: https://www.geeksforgeeks.org/quick-sort/
10 | */
11 | async function partition(arr, htmlElements, start, end) {
12 | const pivotIndex = start
13 | const pivotElement = arr[pivotIndex]
14 |
15 | htmlElements[start].classList.add(LEFT_ITEM_CSS_CLASS)
16 | htmlElements[pivotIndex].classList.add(SAME_ITEM_CSS_CLASS)
17 | htmlElements[end].classList.add(RIGHT_ITEM_CSS_CLASS)
18 |
19 | await wait()
20 |
21 | while (start < end) {
22 | while (start < arr.length && arr[start] <= pivotElement) {
23 | if (htmlElements[start]) {
24 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS)
25 | htmlElements[start].classList.remove(RIGHT_ITEM_CSS_CLASS)
26 | }
27 |
28 | start++
29 |
30 | if (htmlElements[start]) {
31 | htmlElements[start].classList.add(LEFT_ITEM_CSS_CLASS)
32 | }
33 |
34 | await wait()
35 | }
36 |
37 | while (arr[end] > pivotElement) {
38 | if (htmlElements[end]) {
39 | htmlElements[end].classList.remove(LEFT_ITEM_CSS_CLASS)
40 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS)
41 | }
42 |
43 | end--
44 |
45 | if (htmlElements[end]) {
46 | htmlElements[end].classList.add(RIGHT_ITEM_CSS_CLASS)
47 | }
48 |
49 | await wait()
50 | }
51 |
52 | if (start < end) {
53 | swap(arr, start, end)
54 |
55 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS)
56 | htmlElements[start].classList.add(RIGHT_ITEM_CSS_CLASS)
57 |
58 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS)
59 | htmlElements[end].classList.add(LEFT_ITEM_CSS_CLASS)
60 |
61 | htmlElements[start].style.height = `${arr[start]}px`
62 | htmlElements[end].style.height = `${arr[end]}px`
63 |
64 | await wait()
65 | }
66 | }
67 |
68 | if (htmlElements[start]) {
69 | htmlElements[start].classList.remove(LEFT_ITEM_CSS_CLASS)
70 | }
71 |
72 | swap(arr, pivotIndex, end)
73 |
74 | htmlElements[pivotIndex].classList.remove(SAME_ITEM_CSS_CLASS)
75 | htmlElements[end].classList.remove(RIGHT_ITEM_CSS_CLASS)
76 |
77 | htmlElements[pivotIndex].style.height = `${arr[pivotIndex]}px`
78 | htmlElements[end].style.height = `${arr[end]}px`
79 |
80 | return end
81 | }
82 |
83 |
84 | /*
85 | * quick sort visualization
86 | */
87 | async function quickSort(arr, htmlElements, start = 0, end = arr.length - 1) {
88 | if (start >= end) return
89 |
90 | const p = await partition(arr, htmlElements, start, end)
91 |
92 | await quickSort(arr, htmlElements, start, p - 1)
93 | await quickSort(arr, htmlElements, p + 1, end)
94 | }
95 |
96 | export { quickSort }
97 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 | Visualization of common sorting algorithms. 15 | Source code is available on 16 | GitHub, 17 | made by Vladimir Nikitin 18 |
19 |