1+ class algorithmVisualiser {
2+ configVariables ( ) { // Bodge for ES6 not supporting Public Field Declarations
3+ self . dataSet = {
4+ data : [ ] ,
5+ comp : [ ] ,
6+ compCount : 0 ,
7+ swap : [ ] ,
8+ swapCount : 0 ,
9+ iteration : 0
10+ } ;
11+ self . options = {
12+ size : 128 ,
13+ delay : 16 ,
14+ structure : "Random" ,
15+ algorithm : "Bubble Sort" ,
16+ gui : false ,
17+ fps : false ,
18+ lineColor : "#0000FF" ,
19+ compColor : "#FF0000" ,
20+ swapColor : "#00FF00" ,
21+ lineWidth : 2.5
22+ } ;
23+ }
24+ constructor ( canvas , opts = { } ) {
25+ self = this ;
26+ self . configVariables ( ) ;
27+ self . configDynamicFunctions ( ) ;
28+ self . options = { ...self . options , ...opts } ; // Apply construction options
29+ self . ctx = canvas . getContext ( '2d' ) ;
30+
31+ if ( self . options . gui ) {
32+ var gui = new dat . GUI ( ) ;
33+ gui . add ( self . options , 'algorithm' , self . algorithms . map ( x => x . name ) ) . name ( "Algorithm" ) ;
34+ gui . add ( self . options , 'structure' , self . structures . map ( x => x . name ) ) . name ( "Structure" ) . onFinishChange ( self . reset ) ;
35+ gui . add ( self . options , 'size' , 64 , 1024 , 64 ) . name ( "Size" ) . onChange ( self . reset ) ;
36+ gui . add ( self . options , 'delay' , 0 , 64 ) . name ( "Delay (ms)" ) ;
37+ gui . add ( self , 'reset' ) . name ( "Reset" ) ;
38+ gui . add ( self , 'startStop' ) . name ( "Start / Stop" ) ;
39+
40+ let advFolder = gui . addFolder ( "Advanced" ) ;
41+ advFolder . addColor ( self . options , 'lineColor' ) ;
42+ advFolder . addColor ( self . options , 'compColor' ) ;
43+ advFolder . addColor ( self . options , 'swapColor' ) ;
44+ advFolder . add ( self . options , 'lineWidth' , 0 , 15 ) ;
45+
46+ if ( self . options . fps != false && self . options . gui ) {
47+ self . options . fps = new Stats ( ) ;
48+ self . options . fps . domElement . height = '48px' ;
49+ [ ] . forEach . call ( self . options . fps . domElement . children , ( child ) => ( child . style . display = '' ) ) ;
50+
51+ var perfFolder = gui . addFolder ( "Performance" ) ;
52+ var perfLi = document . createElement ( "li" ) ;
53+ self . options . fps . domElement . style . position = "static" ;
54+ perfLi . appendChild ( self . options . fps . domElement ) ;
55+ perfLi . classList . add ( "gui-stats" ) ;
56+ perfFolder . __ul . appendChild ( perfLi ) ;
57+ }
58+
59+ }
60+
61+ if ( self . options . fps != false && ! ( self . options . gui ) ) {
62+ self . options . fps = new Stats ( ) ;
63+ self . options . fps . showPanel ( 0 ) ; // 0: fps, 1: ms, 2: mb, 3+: custom
64+ self . options . fps . domElement . style = "position:fixed;top:0;left:0;" ;
65+ document . body . appendChild ( self . options . fps . domElement ) ;
66+ }
67+
68+ self . reset ( ) ;
69+ self . draw ( ) ;
70+ }
71+
72+ startStop ( ) {
73+ self . algorithms . find ( x => x . name === self . options . algorithm ) . algorithm ( ) ;
74+ }
75+
76+ reset ( ) {
77+ self . structures . find ( x => x . name === self . options . structure ) . func ( ) ;
78+ self . dataSet . iteration = 0 ;
79+ self . dataSet . swapCount = 0 ;
80+ self . dataSet . compCount = 0 ;
81+ }
82+
83+ draw ( ) {
84+ if ( self . options . fps != false ) { self . options . fps . begin ( ) ; }
85+ var ctx = self . ctx ;
86+ var width = ctx . canvas . width = ctx . canvas . clientWidth ;
87+ var height = ctx . canvas . height = ctx . canvas . clientHeight ;
88+ var scaleY = ( height / Math . max ( ...self . dataSet . data ) ) ;
89+ var lineWidth = self . options . lineWidth || ( width / self . dataSet . data . length ) + 0.5 ;
90+
91+
92+ for ( let i = 0 ; i < self . dataSet . data . length ; i ++ ) { // For every element in the dataset, draw its line
93+ ctx . strokeStyle = self . options . lineColor ;
94+ ctx . lineWidth = lineWidth ;
95+ ctx . beginPath ( ) ;
96+
97+ if ( self . dataSet . comp . includes ( i ) ) { // If the line is currently in a comparison, draw it in that colour
98+ ctx . lineWidth = lineWidth * 3 ;
99+ ctx . strokeStyle = self . options . compColor ;
100+ }
101+
102+ if ( self . dataSet . swap . includes ( i ) ) { // If the line is currently in a swap, draw it in that colour
103+ ctx . lineWidth = lineWidth * 3 ;
104+ ctx . strokeStyle = self . options . swapColor ;
105+ }
106+
107+ ctx . moveTo ( i * ( width / self . dataSet . data . length ) , height ) ;
108+ ctx . lineTo ( i * ( width / self . dataSet . data . length ) , height - ( self . dataSet . data [ i ] * scaleY ) ) ;
109+
110+ ctx . stroke ( ) ;
111+ }
112+
113+ ctx . font = "15px Arial" ;
114+ var textStr = `${ self . options . algorithm } Iteration: ${ self . dataSet . iteration } Comparisons: ${ self . dataSet . compCount } Swaps: ${ self . dataSet . swapCount } ` ;
115+ ctx . fillText ( textStr , 10 , 25 ) ;
116+
117+ if ( self . options . fps != false ) { self . options . fps . end ( ) ; }
118+ window . requestAnimationFrame ( ( ) => self . draw ( ) ) ;
119+ //window.requestAnimationFrame( this.draw.bind(this) ); // Fix "this" binding
120+ }
121+
122+ async wait ( time ) {
123+ return new Promise ( function ( resolve ) {
124+ setTimeout ( resolve , time ) ;
125+ } ) ;
126+ }
127+
128+ configDynamicFunctions ( ) { // Bodge for ES6 not supporting Public Field Declarations
129+ self . structures = [
130+ { name :"Random" , func : ( ) => {
131+ self . dataSet . data = Array . from ( { length : self . options . size } , ( ) => Math . floor ( Math . random ( ) * self . options . size ) ) ;
132+ } } ,
133+ { name :"Random Unique" , func : ( ) => {
134+ self . dataSet . data = Array . from ( new Array ( self . options . size ) , ( v , i ) => i ) ;
135+ for ( let i = self . dataSet . data . length - 1 ; i > 0 ; i -- ) {
136+ const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
137+ [ self . dataSet . data [ i ] , self . dataSet . data [ j ] ] = [ self . dataSet . data [ j ] , self . dataSet . data [ i ] ] ;
138+ }
139+ } } ,
140+ { name :"Reverse Order" , func : ( ) => {
141+ self . dataSet . data = Array . from ( new Array ( self . options . size ) , ( v , i ) => self . options . size - i ) ;
142+ } } ,
143+ { name :"Nearly Sorted" , func : ( ) => {
144+ // TODO: Make Nearly Sorted Arr
145+ } } ,
146+ { name :"Few Unique" , func : ( ) => {
147+ // TODO: Make Arr with a few unique values
148+ } }
149+ ]
150+
151+ self . algorithms = [
152+
153+ { name :"Bubble Sort" , algorithm : async ( ) => {
154+ for ( let i = ( self . dataSet . data . length - 1 ) ; i >= 0 ; i -- ) {
155+ for ( let j = ( self . dataSet . data . length - i ) ; j > 0 ; j -- ) {
156+ self . dataSet . iteration ++ ;
157+ self . dataSet . compCount ++ ;
158+ self . dataSet . swap = [ ] ;
159+ self . dataSet . comp = [ j - 1 , j ] ;
160+ if ( self . dataSet . data [ j ] < self . dataSet . data [ j - 1 ] ) {
161+ [ self . dataSet . data [ j - 1 ] , self . dataSet . data [ j ] ] = [ self . dataSet . data [ j ] , self . dataSet . data [ j - 1 ] ] ;
162+ self . dataSet . swap = [ j - 1 , j ] ;
163+ self . dataSet . swapCount ++ ;
164+ }
165+ await self . wait ( self . options . delay ) ; // Delay before next iteration
166+ }
167+ }
168+ self . dataSet . swap = [ ] ; // Finished Empty active data
169+ self . dataSet . comp = [ ] ; // Finished Empty active data
170+ } } ,
171+
172+ { name :"Quick Sort" , algorithm : async ( ) => {
173+
174+ } } ,
175+
176+ { name :"Reverse Order" , algorithm : async ( ) => {
177+
178+ } }
179+
180+ ] ;
181+ }
182+ }
183+
184+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
185+ /* End Class */
186+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
187+
188+ var AV ;
189+
190+ window . onload = ( ) => {
191+ let AVCanvas = document . getElementById ( "algorithm-visualiser" ) ;
192+ AV = new algorithmVisualiser ( AVCanvas , { gui : true , fps : true } ) ;
193+ } ;
0 commit comments