diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ee4fab103342350f01407dcfaaa8c231db4a012d --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'export', + distDir: 'public', + +} + +export default nextConfig diff --git a/src/app/layout.jsx b/src/app/layout.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0471ac699d031f1e92e1328e9fe540ae6bdc91df --- /dev/null +++ b/src/app/layout.jsx @@ -0,0 +1,19 @@ +import Head from "next/head"; + +export const Metadata = { + title: 'Sorting Visualizer', + description: 'Visualize sorting algorithms', +} + +export default function RootLayout({children}) { + return ( + <html lang="en"> + <Head> + <link rel="icon" href="/src/styles/favicon.ico" /> + </Head> + <body> + <div id="root">{children}</div> + </body> + </html> + ) +} \ No newline at end of file diff --git a/src/app/page.jsx b/src/app/page.jsx index 5aae9c65dd569459378c1b24210e9edec916509b..6a8d2519bab6fd0fd1557a3ca375c637ab1fe1a5 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -1,12 +1,9 @@ import SortingVisualizer from '../components/SortingVisualizer.jsx' -import Head from "next/head"; export default function Home() { return ( <main> - <Head> - <link rel="icon" href="/src/styles/favicon.ico" /> - </Head> + <SortingVisualizer /> </main> ) diff --git a/src/components/SortingVisualizer.jsx b/src/components/SortingVisualizer.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6220c820d4175fe72fa171252785ce64c51dd506 --- /dev/null +++ b/src/components/SortingVisualizer.jsx @@ -0,0 +1,156 @@ +"use client"; + +import {useState, useEffect} from 'react'; +import '../styles/SortingVisualizer.css' +import BubbleSort from "../libs/BubbleSort.jsx"; +import RangeSlider from 'react-bootstrap-range-slider'; +import 'react-bootstrap-range-slider/dist/react-bootstrap-range-slider.css'; +import {Dropdown} from 'react-bootstrap'; +import 'bootstrap/dist/css/bootstrap.min.css'; + +function SortingVisualizer() { + + const moduloFive = ((window.innerWidth / 45)).toFixed(0) % 5; + const monitorSize = (window.innerWidth / 45).toFixed(0) - moduloFive; + const [BarNumber, setBarNumber] = useState(() => monitorSize > 100 ? 100 : monitorSize); // max of 100 bars + const [bars, setbars] = useState(() => generateArray(BarNumber)); + const [sorting, setSorting] = useState(false); + const [alreadySorted, setAlreadySorted] = useState(); + const [isSorted, setIsSorted] = useState(false); + const [sortSpeed, setSortSpeed] = useState('40'); + + const SliderWithInputFormControl = () => { + const [sliderValue_intern, setSliderValue_intern] = useState(BarNumber); + return ( + <RangeSlider + value={sliderValue_intern} + step={5} + max={100} + min={5} + disabled={sorting} + onChange={changeEvent => setSliderValue_intern(Number(changeEvent.target.value))} + onAfterChange={() => setBarNumber(sliderValue_intern)} + /> + ); + }; + + useEffect(() => { + resetBars(); + }, [BarNumber]); + + function getRandomArbitrary(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + } + + function generateArray(numberBars) { + return Array.from({length: numberBars}, () => getRandomArbitrary(50, 300)); + } + + async function handleSorting() { + if (sorting) return; + setSorting(true); + await BubbleSort(bars, setbars, setAlreadySorted, sortSpeed); + setSorting(false); + } + + function resetBars() { + setAlreadySorted(1000); + setbars(generateArray(BarNumber)); + let ColoredBars = document.getElementsByClassName('sorted'); + document.getElementById('startButton').innerText = 'Sort!'; + while (ColoredBars.length > 0) { + ColoredBars[0].className = 'bar'; + } + setIsSorted(false); + + + } + + function finishAnimation() { + let ColoredBars = document.getElementsByClassName('sorted'); + while (ColoredBars.length > 0) { + ColoredBars[0].className = 'finished'; + } + } + + function handleButton() { + if (!sorting && !isSorted) { + setAlreadySorted(1000); + handleSorting(); + } else if (isSorted) { + resetBars(); + + } + } + + useEffect(() => { + if (alreadySorted == 0) { + setIsSorted(true); + finishAnimation(); + document.getElementById('startButton').innerText = 'Reset!'; + } + }, [alreadySorted]); + + + return ( + <> + <div className='Container'> + <div id='ToolBar'> + <div className="Label">How many bars?</div> + <SliderWithInputFormControl style={{className: 'range-slider'}}/> + <input type='number' max={100} min={0} disabled={sorting} + onChange={changeEvent => { + let value = Number(changeEvent.target.value); + if (value > 100) value = 100; + if (value < 5) value = 5; + setBarNumber(value); + }} defaultValue={BarNumber} + value={BarNumber} style={{ + width: '42px', + margin: '10px', + borderRadius: '5px', + border: 'none', + textAlign: 'center' + }}/> + <div style={{background: 'gray', height: '70%', marginLeft: '10px', width: '2px'}}></div> + <Dropdown id="dropdown"> + <Dropdown.Toggle + id="dropdown" + disabled={sorting} + > + Sorting-Speed: { + { + 100: 'Slow', + 40: 'Medium', + 0: 'Fast' + }[sortSpeed] || '' + } + </Dropdown.Toggle> + <Dropdown.Menu id="dropdown-menu"> + <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(100)}> + Slow + </Dropdown.Item> + <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(40)}> + Medium + </Dropdown.Item> + <Dropdown.Item id="dropdown-item" onClick={() => setSortSpeed(0)}> + Fast + </Dropdown.Item> + </Dropdown.Menu> + </Dropdown> + <button id='startButton' className="Button" disabled={sorting} + style={{background: `${sorting ? 'red' : ''}`}} onClick={() => { + handleButton() + }}>{sorting ? 'Sorting...' : 'Sort!'}</button> + </div> + <div className='DivArray'> + {bars.map((height, index) => (<div key={index} data-key={index} + className={`bar + ${alreadySorted <= index ? 'sorted' : ''}`} + style={{height: `${height}px`}}></div>))} + </div> + + </div> + </> + ) +}; +export default SortingVisualizer; \ No newline at end of file diff --git a/src/libs/BubbleSort.jsx b/src/libs/BubbleSort.jsx new file mode 100644 index 0000000000000000000000000000000000000000..03b4c9da8fc6afeeaaf352e2be91221e9807c183 --- /dev/null +++ b/src/libs/BubbleSort.jsx @@ -0,0 +1,23 @@ +async function BubbleSort(EingangsArray, updateBars, setColor, sortSpeed){ + const len = EingangsArray.length; + for(let i = 0; i< len -1; ++i){ + for(let k=0; k< len -1-i; k++){ + let tmp; + + if (k == len -2-i){ + setColor([(len-1-i)]); + } + if(EingangsArray[k] > EingangsArray[k+1]){ + tmp = EingangsArray[k]; + EingangsArray[k] = EingangsArray[k+1]; + EingangsArray[k+1] = tmp; + } + updateBars([...EingangsArray]); + await new Promise(resolve => setTimeout(resolve, sortSpeed)); // Pause für Animation + } + } + setColor([(len-len)]); + console.log(EingangsArray); + return EingangsArray; +} +export default BubbleSort; \ No newline at end of file diff --git a/src/styles/SortingVisualizer.css b/src/styles/SortingVisualizer.css new file mode 100644 index 0000000000000000000000000000000000000000..4d120a84518fce493e22b832d7f68490293d2676 --- /dev/null +++ b/src/styles/SortingVisualizer.css @@ -0,0 +1,165 @@ +body { + margin: 0; +} + +.Container { + display: flex; + flex-direction: column; + align-items: center; +} + + +.bar { + background-color: blue; + display: inline-block; + height: 100px; + flex: 1 1 20px; + min-width: 2px; +} + +.sorted { + background-color: deeppink; + animation: ScaleAnimation 0.5s ease-in-out; + +} + +.finished { + background-color: lawngreen; + flex: 1 1 20px; + min-width: 2px; + margin: 2px; + display: inline-block; + animation: GreenScaleAnimation 1s ease-in; + +} + +.range-slider { + padding: 0px; +} + +#ToolBar { + height: 50px; + width: 100%; + background: #34495e; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + +} + +#startButton { + height: 78%; + width: 85px; + font-size: 12px; + margin: 10px; + background: #1abc9c; + text-align: center; + display: grid; + place-items: center; + border-radius: 5px; + border: none; + color: white; + font-size: 14px; + font-family: Arial, sans-serif; + font-weight: bold; + +} + +button:active { + box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.46); + transform: translateY(2px); +} + +.Label { + margin: 10px; + font-size: 15px; + font-family: Arial, sans-serif; + font-weight: bold; + color: white; +} + +.DivArray { + transform: scaleY(-1); + display: flex; + flex-wrap: nowrap; + justify-content: center; + gap: 2px; + width: 90%; + margin-top: 5px; +} + +#dropdown-menu { + background-color: #34495e; + border: none; + border-radius: 5px; + margin-top: 3px; +} + + +#dropdown-item { + color: white; + font-size: 16px; + font-weight: normal; + border-radius: 5px; +} + +#dropdown-item:hover { + background-color: #1abc9c; + color: white; +} + +#dropdown { + background: none; + border: none; + border-radius: 0px; + color: white; + fontSize: 14px; + fontWeight: normal; + padding: 0; + height: 100%; + display: flex; + alignItems: center; + place-items: center; + margin-inline: 10px; + width: 190px; +} + +#dropdown:hover { + color: #1abc9c; +} + +#dropdown:active { + box-shadow: none; + transform: none; +} + +#dropdown.show { + background-color: #1abc9c; + color: white; +} + + +@keyframes ScaleAnimation { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + 100% { + transform: scale(1); + } +} + +@keyframes GreenScaleAnimation { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} diff --git a/src/styles/favicon.ico b/src/styles/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3acbd6c1b785768b3563e233314d14e7e75ac21 Binary files /dev/null and b/src/styles/favicon.ico differ