@@ -12,7 +12,8 @@ class Node {
12
12
this . level = 0 ;
13
13
14
14
this . extras = {
15
- new : true
15
+ initialized : false ,
16
+ entered : false
16
17
} ;
17
18
}
18
19
}
@@ -44,6 +45,20 @@ class BST {
44
45
if ( ! node ) return 0 ;
45
46
return node . height ;
46
47
}
48
+
49
+ static breadthFirstTraverse ( node , callback ) {
50
+ if ( ! node ) return ;
51
+ const deque = [ ] ;
52
+
53
+ deque . push ( node ) ;
54
+
55
+ while ( deque . length ) {
56
+ const current = deque . shift ( ) ;
57
+ callback ( current ) ;
58
+ if ( current . left ) deque . push ( current . left ) ;
59
+ if ( current . right ) deque . push ( current . right ) ;
60
+ }
61
+ }
47
62
}
48
63
49
64
let tree = null ;
@@ -64,7 +79,13 @@ let mouse = {
64
79
65
80
const camera = {
66
81
x : 0 ,
67
- y : 0
82
+ y : 0 ,
83
+ bounds : {
84
+ minX : Infinity ,
85
+ maxX : - Infinity ,
86
+ minY : Infinity ,
87
+ maxY : - Infinity
88
+ }
68
89
} ;
69
90
70
91
const dimensions = {
@@ -83,7 +104,39 @@ const resize = () => {
83
104
const radius = 20 ;
84
105
const padding = 20 ;
85
106
86
- const renderNode = ( node , parent ) => {
107
+ const calculateNodePosition = ( node , parent ) => {
108
+ return parent
109
+ ? {
110
+ x : parent . extras . x + ( parent . left === node ? + 1 : - 1 ) * ( padding + radius * 2 ) ,
111
+ y : parent . extras . y + padding + radius * 2
112
+ } : {
113
+ x : 0 ,
114
+ y : - ( dimensions . height / 2 ) + padding + radius
115
+ } ;
116
+ } ;
117
+
118
+ const updateNode = ( node , parent = null ) => {
119
+ if ( ! node ) return ;
120
+
121
+ if ( ! node . extras . initialized ) {
122
+ node . extras . multiplier = 0 ;
123
+ const { x, y} = calculateNodePosition ( node , parent ) ;
124
+ node . extras . x = x ;
125
+ node . extras . y = y ;
126
+ updateCameraBounds ( tree ) ; // TODO: Find a better way to do this
127
+ node . extras . initialized = true ;
128
+ } else {
129
+ if ( ! node . extras . entered ) {
130
+ node . extras . multiplier += 0.03 ;
131
+ if ( node . extras . multiplier > 1 ) node . extras . entered = true ;
132
+ }
133
+ }
134
+
135
+ updateNode ( node . left , node ) ;
136
+ updateNode ( node . right , node ) ;
137
+ } ;
138
+
139
+ const renderNode = ( node , parent = null ) => {
87
140
if ( ! node ) return ;
88
141
89
142
ctx . save ( ) ;
@@ -93,41 +146,61 @@ const renderNode = (node, parent) => {
93
146
94
147
ctx . beginPath ( ) ;
95
148
96
- if ( node . extras . new ) {
97
- node . extras . radius = 0 ;
98
- if ( parent ) {
99
- node . extras . x = parent . extras . x + ( parent . left === node ? + 1 : - 1 ) * ( padding + radius * 2 ) ;
100
- node . extras . y = parent . extras . y + padding + radius * 2 ;
101
- } else {
102
- node . extras . x = 0 ;
103
- node . extras . y = - ( dimensions . height / 2 ) + padding + radius ;
104
- }
105
- node . extras . new = false ;
106
- } else {
107
- if ( node . extras . radius < radius ) {
108
- node . extras . radius += 1 ;
109
- }
110
- }
111
149
112
150
let { x, y } = node . extras ;
113
151
114
152
const textMeasurements = ctx . measureText ( node . value ) ;
115
153
116
- ctx . arc ( x , y , node . extras . radius , 0 , Math . PI * 2 ) ;
154
+ ctx . arc ( x , y , node . extras . multiplier * radius , 0 , Math . PI * 2 ) ;
117
155
ctx . fillText ( node . value , x - textMeasurements . width / 2 , y + ctx . measureText ( "M" ) . width / 2 - 2 ) ;
118
156
ctx . stroke ( ) ;
157
+
158
+
159
+ if ( parent ) {
160
+ ctx . save ( ) ;
161
+
162
+ ctx . strokeStyle = "black" ;
163
+
164
+ const dx = ( parent . extras . x - node . extras . x )
165
+ dy = ( parent . extras . y - node . extras . y ) ;
166
+
167
+
168
+ const angle = Math . atan2 ( dy , dx ) ;
169
+ const start = {
170
+ x : parent . extras . x - radius * Math . cos ( angle ) ,
171
+ y : parent . extras . y - radius * Math . sin ( angle )
172
+ } ;
173
+
174
+ const d = Math . sqrt ( Math . pow ( dx , 2 ) , Math . pow ( dy , 2 ) ) - radius ;
175
+
176
+ const end = {
177
+ x : node . extras . x - ( radius + ( d - d * node . extras . multiplier ) ) * Math . cos ( angle + Math . PI ) ,
178
+ y : node . extras . y - ( radius + ( d - d * node . extras . multiplier ) ) * Math . sin ( angle + Math . PI )
179
+ } ;
180
+
181
+ ctx . beginPath ( ) ;
182
+ ctx . moveTo ( start . x , start . y ) ;
183
+ ctx . lineTo ( end . x , end . y ) ;
184
+ ctx . stroke ( ) ;
185
+
186
+ ctx . restore ( ) ;
187
+ }
188
+
119
189
ctx . restore ( ) ;
120
190
121
191
renderNode ( node . left , node ) ;
122
192
renderNode ( node . right , node ) ;
123
193
} ;
124
194
125
- const renderTree = ( ) => {
126
- if ( ! tree ) return ;
195
+ const updateTree = ( tree ) => {
196
+ updateNode ( tree ) ;
197
+ } ;
127
198
128
- renderNode ( tree , null ) ;
199
+ const renderTree = ( tree ) => {
200
+ renderNode ( tree ) ;
129
201
} ;
130
202
203
+
131
204
const render = ( ) => {
132
205
const hWidth = dimensions . width / 2 ,
133
206
hHeight = dimensions . height / 2 ;
@@ -137,12 +210,23 @@ const render = () => {
137
210
ctx . translate ( hWidth + camera . x , hHeight + camera . y ) ;
138
211
ctx . clearRect ( - hWidth * 10 , - hHeight * 10 , dimensions . width * 10 , dimensions . height * 10 ) ;
139
212
140
- renderTree ( ) ;
213
+ updateTree ( tree ) ;
214
+ renderTree ( tree ) ;
141
215
142
216
ctx . restore ( ) ;
143
217
window . requestAnimationFrame ( render ) ;
144
218
} ;
145
219
220
+
221
+ const updateCameraBounds = ( tree ) => {
222
+ BST . breadthFirstTraverse ( tree , ( item ) => {
223
+ camera . bounds . minX = Math . min ( item . extras . x , camera . bounds . minX ) ;
224
+ camera . bounds . minY = Math . min ( item . extras . y , camera . bounds . minY ) ;
225
+ camera . bounds . maxX = Math . max ( item . extras . x , camera . bounds . maxX ) ;
226
+ camera . bounds . maxY = Math . max ( item . extras . y , camera . bounds . maxY ) ;
227
+ } ) ;
228
+ } ;
229
+
146
230
window . addEventListener ( "resize" , ( ) => {
147
231
resize ( ) ;
148
232
} ) ;
@@ -164,15 +248,33 @@ window.addEventListener("mouseup", ({ clientX, clientY }) => {
164
248
165
249
// This is temporary
166
250
const dragDistance = Math . sqrt ( Math . pow ( clientX - mouse . downPos . x , 2 ) , Math . pow ( clientY - mouse . downPos . y , 2 ) ) ;
167
- if ( dragDistance < 8 ) {
168
- BST . push ( tree , Math . round ( Math . random ( ) * 100 ) ) . then ( ( t ) => tree = t ) ;
251
+ if ( dragDistance < 2 ) {
252
+ ( ( async ( ) => {
253
+ tree = await BST . push ( tree , Math . round ( Math . random ( ) * 100 ) ) ;
254
+ } ) ( ) )
169
255
}
170
256
} ) ;
171
257
172
258
window . addEventListener ( "mousemove" , ( { clientX, clientY } ) => {
173
259
if ( mouse . isDown ) {
174
- camera . x = ( clientX - mouse . downPos . x ) + mouse . oldCamera . x ;
175
- camera . y = ( clientY - mouse . downPos . y ) + mouse . oldCamera . y ;
260
+ const newX = ( clientX - mouse . downPos . x ) + mouse . oldCamera . x ,
261
+ newY = ( clientY - mouse . downPos . y ) + mouse . oldCamera . y ;
262
+
263
+ // I have no idea how this even works. it just does
264
+ const halfWidth = dimensions . width / 2 ,
265
+ halfHeight = dimensions . height / 2 ,
266
+ bxa = newX + halfWidth + camera . bounds . maxX ,
267
+ bxb = newX - halfWidth + camera . bounds . minX ,
268
+ bya = newY + halfHeight + camera . bounds . maxY ,
269
+ byb = newY - halfHeight + camera . bounds . minY ;
270
+
271
+ if ( bxa < 0 ) camera . x = - halfWidth - camera . bounds . maxX ;
272
+ else if ( bxb > 0 ) camera . x = halfWidth - camera . bounds . minX ;
273
+ else camera . x = newX ;
274
+
275
+ if ( bya < 0 ) camera . y = - halfHeight - camera . bounds . maxY ;
276
+ else if ( byb > 0 ) camera . y = halfHeight - camera . bounds . minY ;
277
+ else camera . y = newY ;
176
278
}
177
279
} ) ;
178
280
0 commit comments