1717 --sidebar-width : 310px ;
1818 --sidebar-bg : # fbfdff ;
1919 --sidebar-border : # e6eef9 ;
20+ --line-color : rgba (153 , 102 , 255 , 0.95 ); /* light purple line */
21+ --line-fill : rgba (153 , 102 , 255 , 0.18 ); /* translucent fill */
2022 }
2123
2224 html , body {
165167 < p class ="subtitle "> Modular Java web server and Telnet front end for Java 21. Virtual threads, NIO file handling, and a command‑driven Telnet shell with a centralized < code > Main.java</ code > entry point.</ p >
166168 </ header >
167169
168- <!-- Progress / commits chart card -->
170+ <!-- Progress / commits line chart card -->
169171 < section id ="progress " class ="progress-card " aria-labelledby ="progressTitle ">
170172 < div class ="progress-header ">
171173 < div >
@@ -272,7 +274,6 @@ <h2>11. Summary</h2>
272274 const saveTokenBtn = document . getElementById ( 'saveTokenBtn' ) ;
273275 const clearTokenBtn = document . getElementById ( 'clearTokenBtn' ) ;
274276
275- // store token in sessionStorage only
276277 function getToken ( ) {
277278 return sessionStorage . getItem ( 'gh_token' ) || '' ;
278279 }
@@ -292,16 +293,16 @@ <h2>11. Summary</h2>
292293 noteEl . textContent = 'Token cleared.' ;
293294 } ) ;
294295
295- // Utility: floor date to week start (Sunday) in UTC
296+ // Utility: week start (Sunday) in UTC
296297 function weekStartUTC ( date ) {
297298 const d = new Date ( Date . UTC ( date . getUTCFullYear ( ) , date . getUTCMonth ( ) , date . getUTCDate ( ) ) ) ;
298- const day = d . getUTCDay ( ) ; // 0 (Sun) - 6
299+ const day = d . getUTCDay ( ) ;
299300 d . setUTCDate ( d . getUTCDate ( ) - day ) ;
300301 d . setUTCHours ( 0 , 0 , 0 , 0 ) ;
301302 return d . getTime ( ) ;
302303 }
303304
304- // Fetch commits via GitHub REST API (dynamic): paginate commits since 52 weeks ago
305+ // Fetch commits via GitHub REST API (paginated) since ISO date
305306 async function fetchCommitsSince ( sinceIso ) {
306307 const perPage = 100 ;
307308 let page = 1 ;
@@ -313,7 +314,6 @@ <h2>11. Summary</h2>
313314 const url = `${ baseUrl } ?since=${ encodeURIComponent ( sinceIso ) } &per_page=${ perPage } &page=${ page } ` ;
314315 const res = await fetch ( url , { headers } ) ;
315316 if ( res . status === 403 ) {
316- // rate limited or forbidden
317317 const reset = res . headers . get ( 'x-ratelimit-reset' ) ;
318318 throw new Error ( 'Rate limited or access denied. ' + ( reset ? 'Reset at ' + new Date ( reset * 1000 ) . toLocaleString ( ) : '' ) ) ;
319319 }
@@ -323,50 +323,38 @@ <h2>11. Summary</h2>
323323 const commits = await res . json ( ) ;
324324 if ( ! Array . isArray ( commits ) || commits . length === 0 ) break ;
325325 allCommits = allCommits . concat ( commits ) ;
326- // if fewer than perPage returned, no more pages
327326 if ( commits . length < perPage ) break ;
328327 page ++ ;
329- // safety: avoid infinite loop
330328 if ( page > 50 ) break ;
331329 }
332330 return allCommits ;
333331 }
334332
335- // Aggregate commits into 52 weekly buckets (weekStart timestamp -> count)
333+ // Aggregate commits into weekly buckets
336334 function aggregateWeekly ( commits , sinceTs ) {
337- // Build map of weekStart -> count
338335 const weeksMap = new Map ( ) ;
339- // initialize 52 weeks from sinceTs to now
340336 const now = Date . now ( ) ;
341337 const oneWeekMs = 7 * 24 * 60 * 60 * 1000 ;
342338 for ( let t = sinceTs ; t <= now ; t += oneWeekMs ) {
343339 weeksMap . set ( t , 0 ) ;
344340 }
345341 commits . forEach ( c => {
346- // commit.commit.author.date is ISO string
347342 const dateStr = c . commit && c . commit . author && c . commit . author . date ;
348343 if ( ! dateStr ) return ;
349344 const d = new Date ( dateStr ) ;
350345 const wk = weekStartUTC ( d ) ;
351- // if wk earlier than sinceTs, skip
352346 if ( wk < sinceTs ) return ;
353- // find nearest bucket (wk)
354- if ( ! weeksMap . has ( wk ) ) {
355- // if not present (edge cases), add it
356- weeksMap . set ( wk , 0 ) ;
357- }
358347 weeksMap . set ( wk , ( weeksMap . get ( wk ) || 0 ) + 1 ) ;
359348 } ) ;
360- // Convert map to sorted array of {weekStart, total}
361349 const arr = Array . from ( weeksMap . entries ( ) ) . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] ) . map ( ( [ weekStart , total ] ) => ( { weekStart, total} ) ) ;
362350 return arr ;
363351 }
364352
365- // Draw bar chart on canvas (responsive)
366- function drawChart ( weeklyData ) {
353+ // Draw a smooth line chart with translucent fill
354+ function drawLineChart ( weeklyData ) {
367355 const totals = weeklyData . map ( w => w . total ) ;
368356 const max = Math . max ( ...totals , 1 ) ;
369- const padding = 12 ;
357+ const padding = 28 ;
370358 const deviceRatio = window . devicePixelRatio || 1 ;
371359 const clientW = commitsCanvas . clientWidth ;
372360 const clientH = commitsCanvas . clientHeight ;
@@ -379,19 +367,51 @@ <h2>11. Summary</h2>
379367 ctx . fillStyle = '#fff' ;
380368 ctx . fillRect ( 0 , 0 , clientW , clientH ) ;
381369
382- const barGap = 2 ;
383- const barCount = totals . length ;
384- const availW = clientW - padding * 2 ;
385- const barWidth = Math . max ( 1 , ( availW - ( barCount - 1 ) * barGap ) / barCount ) ;
386- const availH = clientH - padding * 2 ;
387-
388- for ( let i = 0 ; i < barCount ; i ++ ) {
389- const x = padding + i * ( barWidth + barGap ) ;
390- const barH = ( totals [ i ] / max ) * availH ;
391- const y = clientH - padding - barH ;
392- const t = totals [ i ] / max ;
393- ctx . fillStyle = `rgba(75,156,211,${ 0.35 + 0.65 * t } )` ;
394- ctx . fillRect ( x , y , barWidth , barH ) ;
370+ const availW = clientW - padding * 2 ;
371+ const availH = clientH - padding * 2 ;
372+ const pointCount = totals . length ;
373+ const stepX = pointCount > 1 ? availW / ( pointCount - 1 ) : availW ;
374+
375+ // compute points
376+ const points = totals . map ( ( v , i ) => {
377+ const x = padding + i * stepX ;
378+ const y = padding + ( 1 - ( v / max ) ) * availH ;
379+ return { x, y, v } ;
380+ } ) ;
381+
382+ // draw translucent fill under curve
383+ ctx . beginPath ( ) ;
384+ if ( points . length ) {
385+ ctx . moveTo ( points [ 0 ] . x , clientH - padding ) ;
386+ for ( let i = 0 ; i < points . length ; i ++ ) {
387+ const p = points [ i ] ;
388+ ctx . lineTo ( p . x , p . y ) ;
389+ }
390+ ctx . lineTo ( points [ points . length - 1 ] . x , clientH - padding ) ;
391+ ctx . closePath ( ) ;
392+ ctx . fillStyle = getComputedStyle ( document . documentElement ) . getPropertyValue ( '--line-fill' ) || 'rgba(153,102,255,0.18)' ;
393+ ctx . fill ( ) ;
394+ }
395+
396+ // draw line
397+ ctx . beginPath ( ) ;
398+ for ( let i = 0 ; i < points . length ; i ++ ) {
399+ const p = points [ i ] ;
400+ if ( i === 0 ) ctx . moveTo ( p . x , p . y ) ;
401+ else ctx . lineTo ( p . x , p . y ) ;
402+ }
403+ ctx . strokeStyle = getComputedStyle ( document . documentElement ) . getPropertyValue ( '--line-color' ) || 'rgba(153,102,255,0.95)' ;
404+ ctx . lineWidth = 2 ;
405+ ctx . lineJoin = 'round' ;
406+ ctx . lineCap = 'round' ;
407+ ctx . stroke ( ) ;
408+
409+ // draw small circles at points
410+ ctx . fillStyle = getComputedStyle ( document . documentElement ) . getPropertyValue ( '--line-color' ) || 'rgba(153,102,255,0.95)' ;
411+ for ( let p of points ) {
412+ ctx . beginPath ( ) ;
413+ ctx . arc ( p . x , p . y , 2.5 , 0 , Math . PI * 2 ) ;
414+ ctx . fill ( ) ;
395415 }
396416
397417 // axis label
@@ -401,42 +421,37 @@ <h2>11. Summary</h2>
401421 ctx . fillText ( 'Weekly commits' , clientW - 8 , 14 ) ;
402422 }
403423
404- // Main update flow: fetch commits via REST, aggregate, draw, update UI
424+ // Main update flow
405425 async function updateCommits ( ) {
406426 try {
407427 noteEl . textContent = 'Fetching commits via GitHub REST API…' ;
408- // since = 52 weeks ago, aligned to week start
409428 const now = new Date ( ) ;
410429 const oneWeekMs = 7 * 24 * 60 * 60 * 1000 ;
411430 const sinceDate = new Date ( now . getTime ( ) - 52 * oneWeekMs ) ;
412431 const sinceWeekStart = new Date ( weekStartUTC ( sinceDate ) ) ;
413432 const sinceIso = sinceWeekStart . toISOString ( ) ;
414433
415- // fetch commits since that ISO
416434 const commits = await fetchCommitsSince ( sinceIso ) ;
417- // aggregate
418435 const weekly = aggregateWeekly ( commits , sinceWeekStart . getTime ( ) ) ;
419- // ensure we have 52 weeks (if some missing, fill)
436+
437+ // ensure 52 weeks
420438 if ( weekly . length < 52 ) {
421- // build full 52-week array starting at sinceWeekStart
422439 const full = [ ] ;
423440 for ( let i = 0 ; i < 52 ; i ++ ) {
424441 const wk = sinceWeekStart . getTime ( ) + i * oneWeekMs ;
425442 const found = weekly . find ( w => w . weekStart === wk ) ;
426443 full . push ( found ? found : { weekStart : wk , total : 0 } ) ;
427444 }
428- // replace weekly
429445 weekly . splice ( 0 , weekly . length , ...full ) ;
430446 }
431447
432- drawChart ( weekly ) ;
448+ drawLineChart ( weekly ) ;
433449 const totalYear = weekly . reduce ( ( s , w ) => s + w . total , 0 ) ;
434450 const updatedAt = new Date ( ) ;
435451 summaryEl . textContent = `${ totalYear } commits (last 52 weeks) · updated ${ updatedAt . toLocaleString ( ) } ` ;
436452 noteEl . textContent = 'Data fetched from GitHub REST commits endpoint.' ;
437453 } catch ( err ) {
438454 console . warn ( err ) ;
439- // fallback: try to fetch repo summary for some info
440455 try {
441456 const token = getToken ( ) ;
442457 const headers = token ? { Authorization : 'token ' + token } : { } ;
@@ -453,7 +468,7 @@ <h2>11. Summary</h2>
453468 summaryEl . textContent = 'Stats unavailable' ;
454469 noteEl . textContent = 'Unable to fetch commit data. Check network or token.' ;
455470 }
456- // draw placeholder
471+ // placeholder
457472 const clientW = commitsCanvas . clientWidth ;
458473 const clientH = commitsCanvas . clientHeight ;
459474 const deviceRatio = window . devicePixelRatio || 1 ;
@@ -482,7 +497,6 @@ <h2>11. Summary</h2>
482497 window . addEventListener ( 'resize' , ( ) => {
483498 clearTimeout ( resizeTimer ) ;
484499 resizeTimer = setTimeout ( ( ) => {
485- // redraw by calling updateCommits (quick)
486500 updateCommits ( ) . catch ( ( ) => { } ) ;
487501 } , 300 ) ;
488502 } ) ;
0 commit comments