@@ -10,7 +10,7 @@ function getProjectileMotionHTML() {
1010 </div>
1111 <div class="control-group">
1212 <label for="projAngle">Launch Angle (°)</label>
13- <input id="projAngle" type="number" min="1" max="89 " value="45">
13+ <input id="projAngle" type="number" min="1" max="90 " value="45">
1414 </div>
1515 </div>
1616
@@ -121,7 +121,6 @@ function getProjectileMotionHTML() {
121121 </style>
122122 ` ;
123123}
124-
125124function initProjectileMotion ( ) {
126125 const g = 9.81 ;
127126 const canvas = document . getElementById ( 'projectileCanvas' ) ;
@@ -168,17 +167,22 @@ function initProjectileMotion() {
168167 const marginBottom = 35 ;
169168 const marginTop = 20 ;
170169
171- const xMax = Math . max ( ...x , 10 ) * 1.1 ;
172- const yMax = Math . max ( ...y , 10 ) * 1.1 ;
170+ const maxValue = Math . max (
171+ Math . max ( ...x , 10 ) ,
172+ Math . max ( ...y , 10 )
173+ ) * 1.1 ;
174+
175+ const xMax = maxValue ;
176+ const yMax = maxValue ;
173177
174178 const usableWidth = width - marginLeft - 20 ;
175179 const usableHeight = height - marginTop - marginBottom ;
176180
177- // Compute a uniform scale (pixels per meter) so vertical and horizontal scaling is identical
178- const scale = Math . min ( usableWidth / xMax , usableHeight / yMax ) ;
181+ const scaleX = usableWidth / xMax ;
182+ const scaleY = usableHeight / yMax ;
179183
180- const mapX = v => marginLeft + v * scale ;
181- const mapY = v => height - marginBottom - v * scale ;
184+ const mapX = v => marginLeft + v * scaleX ;
185+ const mapY = v => height - marginBottom - v * scaleY ;
182186
183187 ctx . clearRect ( 0 , 0 , width , height ) ;
184188
@@ -196,8 +200,49 @@ function initProjectileMotion() {
196200
197201 ctx . fillStyle = "#64748b" ;
198202 ctx . font = "12px Arial" ;
199- ctx . fillText ( "Height (m)" , 8 , marginTop + 12 ) ;
200- ctx . fillText ( "Distance (m)" , width - 95 , height - 10 ) ;
203+
204+ const xTicks = 5 ;
205+ const yTicks = 5 ;
206+
207+ for ( let t = 0 ; t <= xTicks ; t ++ ) {
208+ const value = ( xMax / xTicks ) * t ;
209+ const xPos = marginLeft + value * scaleX ;
210+
211+ ctx . beginPath ( ) ;
212+ ctx . moveTo ( xPos , height - marginBottom ) ;
213+ ctx . lineTo ( xPos , height - marginBottom + 6 ) ;
214+ ctx . stroke ( ) ;
215+
216+ ctx . fillText (
217+ value . toFixed ( 0 ) ,
218+ xPos - 10 ,
219+ height - marginBottom + 20
220+ ) ;
221+ }
222+
223+ for ( let t = 0 ; t <= yTicks ; t ++ ) {
224+ const value = ( yMax / yTicks ) * t ;
225+ const yPos = height - marginBottom - value * scaleY ;
226+
227+ ctx . beginPath ( ) ;
228+ ctx . moveTo ( marginLeft - 6 , yPos ) ;
229+ ctx . lineTo ( marginLeft , yPos ) ;
230+ ctx . stroke ( ) ;
231+
232+ ctx . fillText (
233+ value . toFixed ( 0 ) ,
234+ marginLeft - 25 ,
235+ yPos + 4
236+ ) ;
237+ }
238+
239+ ctx . fillText ( "Distance (m)" , width / 2 - 40 , height ) ;
240+
241+ ctx . save ( ) ;
242+ ctx . translate ( 15 , height / 2 + 40 ) ;
243+ ctx . rotate ( - Math . PI / 2 ) ;
244+ ctx . fillText ( "Height (m)" , 0 , 0 ) ;
245+ ctx . restore ( ) ;
201246
202247 if ( x . length > 1 ) {
203248 ctx . strokeStyle = "#2563eb" ;
@@ -224,27 +269,30 @@ function initProjectileMotion() {
224269 const speed = Number ( speedInput . value ) ;
225270 const angle = Number ( angleInput . value ) ;
226271
227- // Reset previous states
228272 resultEl . style . color = "" ;
229273
230- // Comprehensive Validation Check
231274 if ( isNaN ( speed ) || speed < 1 || speed > 200 ) {
232275 resultEl . textContent = "❌ Error: Speed must be between 1 and 200 m/s." ;
233276 resultEl . style . color = "#ef4444" ;
234277 return ;
235278 }
236279
237- if ( isNaN ( angle ) || angle < 1 || angle > 89 ) {
238- resultEl . textContent = "❌ Error: Angle must be between 1° and 89 °." ;
280+ if ( isNaN ( angle ) || angle < 1 || angle > 90 ) {
281+ resultEl . textContent = "❌ Error: Angle must be between 1° and 90 °." ;
239282 resultEl . style . color = "#ef4444" ;
240283 return ;
241284 }
242285
243286 const stats = projectileStats ( speed , angle ) ;
244287 const points = trajectoryPoints ( speed , angle ) ;
245288
246- const xMax = Math . max ( ...points . x , 10 ) * 1.1 ;
247- const yMax = Math . max ( ...points . y , 10 ) * 1.1 ;
289+ const maxValue = Math . max (
290+ Math . max ( ...points . x , 10 ) ,
291+ Math . max ( ...points . y , 10 )
292+ ) * 1.1 ;
293+
294+ const xMax = Math . max ( maxValue , 10 ) ;
295+ const yMax = Math . max ( maxValue , 10 ) ;
248296
249297 const marginLeft = 50 ;
250298 const marginBottom = 35 ;
@@ -256,11 +304,16 @@ function initProjectileMotion() {
256304 const usableWidth = width - marginLeft - 20 ;
257305 const usableHeight = height - marginTop - marginBottom ;
258306
259-
260- const scale = Math . min ( usableWidth / xMax , usableHeight / yMax ) ;
307+ const scaleX = usableWidth / xMax ;
308+ const scaleY = usableHeight / yMax ;
309+
310+ const mapX = v => marginLeft + v * scaleX ;
311+ const mapY = v => height - marginBottom - v * scaleY ;
261312
262- const mapX = v => marginLeft + v * scale ;
263- const mapY = v => height - marginBottom - v * scale ;
313+ const maxY = Math . max ( ...points . y ) ;
314+ const peakIndex = points . y . indexOf ( maxY ) ;
315+
316+ let showPeakGuides = false ;
264317
265318 drawTrajectory ( points . x , points . y ) ;
266319
@@ -287,6 +340,52 @@ function initProjectileMotion() {
287340 ctx . lineTo ( width - 20 , height - marginBottom ) ;
288341 ctx . stroke ( ) ;
289342
343+ ctx . fillStyle = "#64748b" ;
344+ ctx . font = "12px Arial" ;
345+
346+ const xTicks = 5 ;
347+ const yTicks = 5 ;
348+
349+ for ( let t = 0 ; t <= xTicks ; t ++ ) {
350+ const value = ( xMax / xTicks ) * t ;
351+ const xPos = marginLeft + value * scaleX ;
352+
353+ ctx . beginPath ( ) ;
354+ ctx . moveTo ( xPos , height - marginBottom ) ;
355+ ctx . lineTo ( xPos , height - marginBottom + 6 ) ;
356+ ctx . stroke ( ) ;
357+
358+ ctx . fillText (
359+ value . toFixed ( 0 ) ,
360+ xPos - 10 ,
361+ height - marginBottom + 20
362+ ) ;
363+ }
364+
365+ for ( let t = 0 ; t <= yTicks ; t ++ ) {
366+ const value = ( yMax / yTicks ) * t ;
367+ const yPos = height - marginBottom - value * scaleY ;
368+
369+ ctx . beginPath ( ) ;
370+ ctx . moveTo ( marginLeft - 6 , yPos ) ;
371+ ctx . lineTo ( marginLeft , yPos ) ;
372+ ctx . stroke ( ) ;
373+
374+ ctx . fillText (
375+ value . toFixed ( 0 ) ,
376+ marginLeft - 25 ,
377+ yPos + 4
378+ ) ;
379+ }
380+
381+ ctx . fillText ( "Distance (m)" , width / 2 - 40 , height ) ;
382+
383+ ctx . save ( ) ;
384+ ctx . translate ( 15 , height / 2 + 40 ) ;
385+ ctx . rotate ( - Math . PI / 2 ) ;
386+ ctx . fillText ( "Height (m)" , 0 , 0 ) ;
387+ ctx . restore ( ) ;
388+
290389 ctx . strokeStyle = "#2563eb" ;
291390 ctx . lineWidth = 3 ;
292391 ctx . beginPath ( ) ;
@@ -303,11 +402,53 @@ function initProjectileMotion() {
303402
304403 if ( i > 0 ) {
305404 const last = i - 1 ;
405+ const ballX = mapX ( points . x [ last ] ) ;
406+ const ballY = mapY ( points . y [ last ] ) ;
306407
307408 ctx . fillStyle = "#ef4444" ;
308409 ctx . beginPath ( ) ;
309- ctx . arc ( mapX ( points . x [ last ] ) , mapY ( points . y [ last ] ) , 6 , 0 , Math . PI * 2 ) ;
410+ ctx . arc ( ballX , ballY , 6 , 0 , Math . PI * 2 ) ;
310411 ctx . fill ( ) ;
412+
413+ if ( last >= peakIndex ) {
414+ showPeakGuides = true ;
415+ }
416+
417+ if ( showPeakGuides ) {
418+ const peakX = mapX ( points . x [ peakIndex ] ) ;
419+ const peakY = mapY ( points . y [ peakIndex ] ) ;
420+
421+ ctx . setLineDash ( [ 5 , 5 ] ) ;
422+ ctx . strokeStyle = "#ef4444" ;
423+ ctx . lineWidth = 1.5 ;
424+
425+ ctx . beginPath ( ) ;
426+ ctx . moveTo ( peakX , peakY ) ;
427+ ctx . lineTo ( peakX , height - marginBottom ) ;
428+ ctx . stroke ( ) ;
429+
430+ ctx . beginPath ( ) ;
431+ ctx . moveTo ( marginLeft , peakY ) ;
432+ ctx . lineTo ( peakX , peakY ) ;
433+ ctx . stroke ( ) ;
434+
435+ ctx . setLineDash ( [ ] ) ;
436+
437+ ctx . fillStyle = "#ef4444" ;
438+ ctx . font = "12px Arial" ;
439+
440+ ctx . fillText (
441+ `Hmax: ${ maxY . toFixed ( 2 ) } m` ,
442+ peakX + 10 ,
443+ peakY - 10
444+ ) ;
445+
446+ ctx . fillText (
447+ `${ points . x [ peakIndex ] . toFixed ( 2 ) } m` ,
448+ peakX - 20 ,
449+ height - marginBottom + 20
450+ ) ;
451+ }
311452 }
312453
313454 i ++ ;
0 commit comments